Sign In

@double-coding/flow2spec

Package Overview
Dependencies
Maintainers
2
Versions
2
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@double-coding/flow2spec - npm Package Compare versions

Comparing version
3.2.11
to
3.2.12
+347
lib/doctor.js
const fs = require("fs");
const path = require("path");
const { AGENTS } = require("./agents");
const {
loadFlow2specConfig,
CONFIG_FILENAME,
} = require("./flow2specConfig");
const { resolveDeveloperContext } = require("./developerId");
const knowledgeEngine = require("./knowledgeEngine");
const STATUS = {
pass: "pass",
warning: "warning",
error: "error",
};
function numericVersion(version) {
return String(version || "")
.replace(/^v/, "")
.split(".")
.slice(0, 3)
.map((part) => Number.parseInt(part, 10) || 0);
}
function compareVersions(left, right) {
const a = numericVersion(left);
const b = numericVersion(right);
for (let index = 0; index < 3; index += 1) {
const difference = (a[index] || 0) - (b[index] || 0);
if (difference !== 0) return difference;
}
return 0;
}
function satisfiesNodeEngine(version, engine) {
const minimum = String(engine || "").match(/>=\s*v?(\d+(?:\.\d+){0,2})/);
if (!minimum) return true;
return compareVersions(version, minimum[1]) >= 0;
}
function makeCheck(id, label, status, message, repair = null, details) {
const check = { id, label, status, message, repair };
if (details !== undefined) check.details = details;
return check;
}
function checkKnowledge(cwd) {
try {
const graph = knowledgeEngine.loadKnowledgeGraph(cwd);
const validation = knowledgeEngine.validateKnowledgeGraph(graph, {
strictRevision: true,
});
const normalized = knowledgeEngine.normalizeRoutingWithGraph(graph);
const routingDrift =
normalized.changed ||
knowledgeEngine.stableStringify(normalized.routing) !==
knowledgeEngine.stableStringify(graph.routing);
const details = {
topicCount: validation.topicCount,
issues: validation.issues,
warnings: validation.warnings,
routingDrift,
};
if (!validation.ok || routingDrift) {
const reasons = [...validation.issues];
if (routingDrift) reasons.push("routing metadata differs from topic frontmatter");
return makeCheck(
"knowledge",
"知识库",
STATUS.error,
`知识图存在 ${reasons.length} 个问题。`,
"运行 flow2spec kb build --fix-topics,再运行 flow2spec kb check --strict。",
details,
);
}
if (validation.warnings.length > 0) {
return makeCheck(
"knowledge",
"知识库",
STATUS.warning,
`知识图可用,但有 ${validation.warnings.length} 条警告。`,
"运行 flow2spec kb check --strict 查看详情。",
details,
);
}
return makeCheck(
"knowledge",
"知识库",
STATUS.pass,
`${validation.topicCount} 个 topic 校验通过,routing 无漂移。`,
null,
details,
);
} catch (error) {
return makeCheck(
"knowledge",
"知识库",
STATUS.error,
error.message || String(error),
"确认 .Knowledge/manifest-routing.json 与其引用的 topic、matcher 均存在且为有效格式。",
);
}
}
function isIgnoredByRootGitignore(cwd, entry) {
const gitignore = path.join(cwd, ".gitignore");
if (!fs.existsSync(gitignore)) return false;
const lines = fs
.readFileSync(gitignore, "utf8")
.split(/\r?\n/)
.map((line) => line.trim())
.filter((line) => line && !line.startsWith("#"));
return lines.includes(entry) || lines.includes(entry.replace(/\/$/, ""));
}
function runDoctor(cwd = process.cwd(), options = {}) {
const pkg = options.package || require("../package.json");
const nodeVersion = options.nodeVersion || process.version;
const knowledgeCheck = options.knowledgeCheck || checkKnowledge;
const checks = [];
const engine = pkg.engines?.node || "";
const runtimeOk = satisfiesNodeEngine(nodeVersion, engine);
checks.push(
makeCheck(
"runtime",
"Node.js",
runtimeOk ? STATUS.pass : STATUS.error,
runtimeOk
? `${nodeVersion} 满足 ${engine || "包要求"}。`
: `${nodeVersion} 不满足 ${engine}。`,
runtimeOk ? null : `升级 Node.js 到满足 ${engine} 的版本。`,
{ version: nodeVersion, required: engine },
),
);
const configPath = path.join(cwd, CONFIG_FILENAME);
let config = null;
if (!fs.existsSync(configPath)) {
checks.push(
makeCheck(
"config",
"项目配置",
STATUS.error,
`缺少 ${CONFIG_FILENAME}。`,
"在项目根运行 flow2spec init。",
),
);
} else {
try {
config = loadFlow2specConfig(cwd);
checks.push(
makeCheck(
"config",
"项目配置",
STATUS.pass,
`${CONFIG_FILENAME} 存在且可解析。`,
null,
{ locale: config.locale },
),
);
} catch (error) {
checks.push(
makeCheck(
"config",
"项目配置",
STATUS.error,
error.message || String(error),
`修正 ${CONFIG_FILENAME} 的 JSON 格式。`,
),
);
}
}
const agentsPath = path.join(cwd, "AGENTS.md");
checks.push(
fs.existsSync(agentsPath)
? makeCheck("agents-entry", "项目入口", STATUS.pass, "根 AGENTS.md 已就绪。")
: makeCheck(
"agents-entry",
"项目入口",
STATUS.error,
"缺少根 AGENTS.md。",
"运行 flow2spec init codex,或重新初始化所需 Agent。",
),
);
const manifestPath = path.join(cwd, ".Knowledge", "manifest-routing.json");
checks.push(
fs.existsSync(manifestPath)
? makeCheck(
"knowledge-entry",
"知识库入口",
STATUS.pass,
".Knowledge/manifest-routing.json 已就绪。",
)
: makeCheck(
"knowledge-entry",
"知识库入口",
STATUS.error,
"缺少 .Knowledge/manifest-routing.json。",
"在项目根运行 flow2spec init。",
),
);
const requiredAgentFiles = {
codex: ["AGENTS.md", "hooks.json"],
claude: ["settings.json"],
cursor: ["hooks.json"],
};
const initializedAgents = Object.entries(AGENTS).filter(([, agent]) =>
fs.existsSync(path.join(cwd, agent.root)),
);
if (initializedAgents.length === 0) {
checks.push(
makeCheck(
"agent-roots",
"Agent 配置",
STATUS.warning,
"未检测到 .codex、.claude 或 .cursor 配置根。",
"运行 flow2spec init <agent> 初始化实际使用的 Agent。",
),
);
} else {
for (const [id, agent] of initializedAgents) {
const missing = (requiredAgentFiles[id] || []).filter(
(file) => !fs.existsSync(path.join(cwd, agent.root, file)),
);
checks.push(
missing.length === 0
? makeCheck(
`agent-${id}`,
`${agent.label} 配置`,
STATUS.pass,
`${agent.root} 初始化文件完整。`,
)
: makeCheck(
`agent-${id}`,
`${agent.label} 配置`,
STATUS.error,
`${agent.root} 缺少 ${missing.join("、")}。`,
`运行 flow2spec init ${id} 补齐配置。`,
{ missing },
),
);
}
}
if (config) {
try {
const context = resolveDeveloperContext(config, {
cwd,
gitIdentity: options.gitIdentity,
skipGit: Boolean(options.gitIdentity),
});
const warnings = [...context.warnings];
if (context.legacy && context.enabled) {
warnings.push("未找到 developerId,将使用 legacy .task/ 根。");
}
checks.push(
makeCheck(
"collaboration",
"协作上下文",
warnings.length > 0 ? STATUS.warning : STATUS.pass,
context.legacy
? `使用 ${context.taskRoot}(${context.enabled ? "legacy" : "协作隔离已关闭"})。`
: `developerId=${context.developerId},TASK_ROOT=${context.taskRoot}。`,
warnings.length > 0
? "在 flow2spec.config.json 配置 collaboration.developerId。"
: null,
{ ...context, warnings },
),
);
} catch (error) {
checks.push(
makeCheck(
"collaboration",
"协作上下文",
STATUS.error,
error.message || String(error),
"修正 flow2spec.config.json 的 collaboration 配置。",
),
);
}
}
const taskIgnored = isIgnoredByRootGitignore(cwd, ".task/");
checks.push(
taskIgnored
? makeCheck("task-ignore", "任务目录", STATUS.pass, ".task/ 已在根 .gitignore 中忽略。")
: makeCheck(
"task-ignore",
"任务目录",
STATUS.warning,
".task/ 未在根 .gitignore 中忽略。",
"在根 .gitignore 中加入 .task/,或重新运行 flow2spec init。",
),
);
checks.push(knowledgeCheck(cwd));
const summary = checks.reduce(
(result, check) => {
if (check.status === STATUS.pass) result.passed += 1;
if (check.status === STATUS.warning) result.warnings += 1;
if (check.status === STATUS.error) result.errors += 1;
return result;
},
{ passed: 0, warnings: 0, errors: 0 },
);
return {
ok: summary.errors === 0,
package: { name: pkg.name, version: pkg.version },
cwd: path.resolve(cwd),
summary,
checks,
};
}
function formatDoctorReport(report) {
const marker = { pass: "[PASS]", warning: "[WARN]", error: "[FAIL]" };
const lines = [
`Flow2Spec Doctor v${report.package.version}`,
`项目: ${report.cwd}`,
"",
];
for (const check of report.checks) {
lines.push(`${marker[check.status]} ${check.label}: ${check.message}`);
if (check.repair) lines.push(` 建议: ${check.repair}`);
}
lines.push(
"",
`结果: ${report.summary.passed} 通过,${report.summary.warnings} 警告,${report.summary.errors} 错误。`,
);
return lines.join("\n");
}
module.exports = {
STATUS,
runDoctor,
formatDoctorReport,
satisfiesNodeEngine,
checkKnowledge,
};
+28
-0

@@ -18,2 +18,3 @@ #!/usr/bin/env node

const knowledgeEngine = require("./lib/knowledgeEngine");
const { runDoctor, formatDoctorReport } = require("./lib/doctor");

@@ -238,2 +239,3 @@ const { execFileSync } = require("child_process");

flow2spec config 打印项目根 ${CONFIG_FILENAME} 的解析结果(缺省值合并后)
flow2spec doctor [--json] 只读检查运行环境、项目初始化、协作上下文与知识库健康
flow2spec kb 知识库协作引擎:status / check / plan / apply / build

@@ -330,2 +332,28 @@ flow2spec version 显示当前 flow2spec 版本

if (sub === "doctor") {
const doctorArgs = args.slice(1);
if (doctorArgs.includes("--help") || doctorArgs.includes("-h")) {
console.log(`
用法:
flow2spec doctor [--json]
只读检查 Node.js、项目配置、Agent 初始化、协作上下文、.task 忽略规则与知识库健康。
警告不阻塞(exit 0),错误会返回 exit 1;本命令不会修改文件或访问网络。
`.trim());
process.exit(0);
}
const unknown = doctorArgs.filter((arg) => arg !== "--json");
if (unknown.length > 0) {
console.error(`doctor 不支持参数:${unknown.join(" ")}`);
process.exit(1);
}
const report = runDoctor(process.cwd());
if (doctorArgs.includes("--json")) {
printJson(report);
} else {
console.log(formatDoctorReport(report));
}
process.exit(report.ok ? 0 : 1);
}
if (sub === "kb") {

@@ -332,0 +360,0 @@ const kbSub = args[1];

+2
-2
{
"name": "@double-coding/flow2spec",
"version": "3.2.11",
"version": "3.2.12",
"description": "在业务仓库初始化「文档驱动、可写回知识库」的 AI 协作骨架:项目根 .Knowledge 承载 stock-docs/req-docs 与机读路由,.cursor/.claude/.codex 写入 f2s-* 规则与技能(含 Karpathy 式编码行为准则,init 同步 rules / Codex topics / skills);init 只落结构与模板,业务内容由各 f2s-* 技能在对话中维护。",

@@ -29,3 +29,3 @@ "homepage": "https://github.com/double-coding-lab/Flow2Spec#readme",

"scripts": {
"test": "node cli.js --help && node cli.js kb check && node scripts/test-knowledge-engine.js && node scripts/test-developer-id.js && node scripts/test-template-knowledge.js && node scripts/test-init-gitignore.js",
"test": "node cli.js --help && node cli.js kb check && node scripts/test-knowledge-engine.js && node scripts/test-developer-id.js && node scripts/test-template-knowledge.js && node scripts/test-init-gitignore.js && node scripts/test-doctor.js",
"sync:agents": "node cli.js init cursor claude codex",

@@ -32,0 +32,0 @@ "prepublishOnly": "node cli.js --help",

@@ -21,3 +21,2 @@ # Flow2Spec

<img alt="npm latest" src="https://img.shields.io/npm/v/@double-coding/flow2spec?label=latest">
<img alt="npm beta" src="https://img.shields.io/npm/v/@double-coding/flow2spec/beta?label=beta">
<img alt="node version" src="https://img.shields.io/node/v/@double-coding/flow2spec">

@@ -33,8 +32,2 @@ <img alt="license" src="https://img.shields.io/npm/l/@double-coding/flow2spec">

Try the current beta:
```bash
npx @double-coding/flow2spec@beta init
```
## Why it exists

@@ -149,3 +142,3 @@

- [Flow2Spec 基础介绍](./docs/Flow2Spec基础介绍.md) — Chinese long-form introduction.
- [Live demo](https://double-coding-lab.github.io/Flow2Spec) — 13-slide HTML presentation.
- [Product website](https://double-coding-lab.github.io/Flow2Spec/en/) — a website-style guide to Flow2Spec's core capabilities and workflow.

@@ -152,0 +145,0 @@ ## License

@@ -21,3 +21,2 @@ # Flow2Spec

<img alt="npm latest" src="https://img.shields.io/npm/v/@double-coding/flow2spec?label=latest">
<img alt="npm beta" src="https://img.shields.io/npm/v/@double-coding/flow2spec/beta?label=beta">
<img alt="node version" src="https://img.shields.io/node/v/@double-coding/flow2spec">

@@ -33,8 +32,2 @@ <img alt="license" src="https://img.shields.io/npm/l/@double-coding/flow2spec">

Try the current beta:
```bash
npx @double-coding/flow2spec@beta init
```
## Why it exists

@@ -158,3 +151,3 @@

- [Flow2Spec 基础介绍](./docs/Flow2Spec基础介绍.md) — Chinese long-form introduction.
- [Live demo](https://double-coding-lab.github.io/Flow2Spec) — 13-slide HTML presentation.
- [Product website](https://double-coding-lab.github.io/Flow2Spec/en/) — a website-style guide to Flow2Spec's core capabilities and workflow.

@@ -161,0 +154,0 @@ ## License

@@ -21,3 +21,2 @@ # Flow2Spec

<img alt="npm latest" src="https://img.shields.io/npm/v/@double-coding/flow2spec?label=latest">
<img alt="npm beta" src="https://img.shields.io/npm/v/@double-coding/flow2spec/beta?label=beta">
<img alt="node version" src="https://img.shields.io/node/v/@double-coding/flow2spec">

@@ -33,8 +32,2 @@ <img alt="license" src="https://img.shields.io/npm/l/@double-coding/flow2spec">

尝试当前 beta:
```bash
npx @double-coding/flow2spec@beta init
```
## 为什么需要它

@@ -158,3 +151,3 @@

- [Flow2Spec Introduction](./docs/en/Flow2Spec-Introduction.md) — 英文长文介绍。
- [在线演示](https://double-coding-lab.github.io/Flow2Spec) — 13 页 HTML PPT。
- [在线产品介绍](https://double-coding-lab.github.io/Flow2Spec) — 网站式产品导览,快速了解核心能力与使用路径。

@@ -161,0 +154,0 @@ ## 协议