spec-manager 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/README.md +16 -10
  2. package/dist/cli/decision.js +2 -2
  3. package/dist/cli/decision.js.map +1 -1
  4. package/dist/cli/project.d.ts.map +1 -1
  5. package/dist/cli/project.js +4 -1
  6. package/dist/cli/project.js.map +1 -1
  7. package/dist/cli/task.d.ts.map +1 -1
  8. package/dist/cli/task.js +38 -5
  9. package/dist/cli/task.js.map +1 -1
  10. package/dist/core/agents.d.ts +6 -0
  11. package/dist/core/agents.d.ts.map +1 -1
  12. package/dist/core/agents.js +65 -25
  13. package/dist/core/agents.js.map +1 -1
  14. package/dist/core/audit.d.ts +5 -1
  15. package/dist/core/audit.d.ts.map +1 -1
  16. package/dist/core/audit.js +7 -3
  17. package/dist/core/audit.js.map +1 -1
  18. package/dist/core/decision.d.ts +1 -0
  19. package/dist/core/decision.d.ts.map +1 -1
  20. package/dist/core/decision.js +6 -3
  21. package/dist/core/decision.js.map +1 -1
  22. package/dist/core/harness.d.ts +1 -0
  23. package/dist/core/harness.d.ts.map +1 -1
  24. package/dist/core/harness.js +11 -35
  25. package/dist/core/harness.js.map +1 -1
  26. package/dist/core/integrity.js +3 -3
  27. package/dist/core/integrity.js.map +1 -1
  28. package/dist/core/invariants.d.ts +4 -2
  29. package/dist/core/invariants.d.ts.map +1 -1
  30. package/dist/core/invariants.js +8 -3
  31. package/dist/core/invariants.js.map +1 -1
  32. package/dist/core/spec-sections.d.ts +10 -0
  33. package/dist/core/spec-sections.d.ts.map +1 -0
  34. package/dist/core/spec-sections.js +43 -0
  35. package/dist/core/spec-sections.js.map +1 -0
  36. package/dist/core/task.d.ts +11 -0
  37. package/dist/core/task.d.ts.map +1 -1
  38. package/dist/core/task.js +94 -11
  39. package/dist/core/task.js.map +1 -1
  40. package/dist/core/usability.d.ts +1 -1
  41. package/dist/core/usability.d.ts.map +1 -1
  42. package/dist/core/usability.js +22 -6
  43. package/dist/core/usability.js.map +1 -1
  44. package/dist/core/validate.d.ts.map +1 -1
  45. package/dist/core/validate.js +40 -0
  46. package/dist/core/validate.js.map +1 -1
  47. package/dist/core/verify.d.ts +44 -0
  48. package/dist/core/verify.d.ts.map +1 -0
  49. package/dist/core/verify.js +138 -0
  50. package/dist/core/verify.js.map +1 -0
  51. package/dist/index.d.ts +2 -0
  52. package/dist/index.d.ts.map +1 -1
  53. package/dist/index.js +2 -0
  54. package/dist/index.js.map +1 -1
  55. package/package.json +4 -2
  56. package/rules/quality-gate.md +2 -2
  57. package/scripts/verify-installed-cli.mjs +63 -0
  58. package/skill/subskills/impl.md +12 -8
@@ -0,0 +1,138 @@
1
+ /**
2
+ * @verify 规则解析与执行。
3
+ * 从 L3 spec 的 ## 验收标准 段中解析 @verify: 标记,自动执行校验。
4
+ */
5
+ import { existsSync, readFileSync } from 'node:fs';
6
+ import { execSync } from 'node:child_process';
7
+ import path from 'node:path';
8
+ /** 匹配 @verify 行,支持有序列表(1. )和无序列表(- )前缀 */
9
+ export const VERIFY_RE = /^(?:\d+\.\s+|- )?@verify:\s*(\w[\w-]*)\((.+)\)\s*$/;
10
+ const COMMAND_TIMEOUT_MS = 30_000;
11
+ /** @verify 类型 → 参数数量 */
12
+ export const VERIFY_TYPE_ARITY = {
13
+ 'file-exists': 1,
14
+ 'export-exists': 2,
15
+ 'command': 1,
16
+ };
17
+ /**
18
+ * 从 spec markdown 的指定段中解析 @verify 规则。
19
+ * 仅解析 ## sectionName 段内的 @verify: 行。
20
+ */
21
+ export function parseVerifyRules(content, sectionName) {
22
+ const rules = [];
23
+ const lines = content.split('\n');
24
+ let inSection = false;
25
+ for (const line of lines) {
26
+ const trimmed = line.trim();
27
+ if (/^##\s+/.test(trimmed)) {
28
+ inSection = trimmed.includes(sectionName);
29
+ continue;
30
+ }
31
+ if (!inSection)
32
+ continue;
33
+ const m = VERIFY_RE.exec(trimmed);
34
+ if (!m)
35
+ continue;
36
+ const [, type, argsStr] = m;
37
+ const args = splitArgs(argsStr);
38
+ if (type === 'file-exists' && args.length === 1) {
39
+ rules.push({ type: 'file-exists', path: args[0] });
40
+ }
41
+ else if (type === 'export-exists' && args.length === 2) {
42
+ rules.push({ type: 'export-exists', file: args[0], symbol: args[1] });
43
+ }
44
+ else if (type === 'command' && args.length === 1) {
45
+ rules.push({ type: 'command', cmd: args[0] });
46
+ }
47
+ }
48
+ return rules;
49
+ }
50
+ /**
51
+ * 执行一组 @verify 规则,返回每条规则的结果。
52
+ * projectRoot 用于解析相对路径。
53
+ */
54
+ export function executeVerifyRules(rules, projectRoot) {
55
+ return rules.map(rule => executeOne(rule, projectRoot));
56
+ }
57
+ function executeOne(rule, projectRoot) {
58
+ switch (rule.type) {
59
+ case 'file-exists': {
60
+ const abs = path.resolve(projectRoot, rule.path);
61
+ const exists = existsSync(abs);
62
+ return {
63
+ rule,
64
+ passed: exists,
65
+ message: exists ? `${rule.path} exists` : `${rule.path} not found`,
66
+ };
67
+ }
68
+ case 'export-exists': {
69
+ const abs = path.resolve(projectRoot, rule.file);
70
+ if (!existsSync(abs)) {
71
+ return { rule, passed: false, message: `${rule.file} not found` };
72
+ }
73
+ const content = readFileSync(abs, 'utf8');
74
+ const re = new RegExp(`export\\s+(?:default\\s+)?(?:function|const|let|var|class|type|interface|enum)\\s+${escapeRegExp(rule.symbol)}\\b`);
75
+ const alsoNamed = new RegExp(`export\\s*\\{[^}]*\\b${escapeRegExp(rule.symbol)}\\b[^}]*\\}`);
76
+ const found = re.test(content) || alsoNamed.test(content);
77
+ return {
78
+ rule,
79
+ passed: found,
80
+ message: found
81
+ ? `${rule.symbol} exported from ${rule.file}`
82
+ : `${rule.symbol} not found in exports of ${rule.file}`,
83
+ };
84
+ }
85
+ case 'command': {
86
+ const result = runCommand(rule.cmd, projectRoot);
87
+ return {
88
+ rule,
89
+ passed: result.exitCode === 0,
90
+ message: result.exitCode === 0
91
+ ? `${rule.cmd} → exit 0`
92
+ : `${rule.cmd} → exit ${result.exitCode}${result.output ? ': ' + result.output : ''}`,
93
+ };
94
+ }
95
+ }
96
+ }
97
+ export function runCommand(cmd, cwd) {
98
+ try {
99
+ execSync(cmd, { cwd, timeout: COMMAND_TIMEOUT_MS, stdio: 'pipe', encoding: 'utf8' });
100
+ return { exitCode: 0, output: '' };
101
+ }
102
+ catch (err) {
103
+ const e = err;
104
+ const exitCode = e.status ?? 1;
105
+ const output = (e.stderr ?? e.stdout ?? '').toString().slice(0, 500);
106
+ return { exitCode, output };
107
+ }
108
+ }
109
+ /** 拆分括号内逗号分隔参数,处理嵌套括号 */
110
+ export function splitArgs(raw) {
111
+ const args = [];
112
+ let depth = 0;
113
+ let current = '';
114
+ for (const ch of raw) {
115
+ if (ch === '(') {
116
+ depth++;
117
+ current += ch;
118
+ }
119
+ else if (ch === ')') {
120
+ depth--;
121
+ current += ch;
122
+ }
123
+ else if (ch === ',' && depth === 0) {
124
+ args.push(current.trim());
125
+ current = '';
126
+ }
127
+ else {
128
+ current += ch;
129
+ }
130
+ }
131
+ if (current.trim())
132
+ args.push(current.trim());
133
+ return args;
134
+ }
135
+ function escapeRegExp(s) {
136
+ return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
137
+ }
138
+ //# sourceMappingURL=verify.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verify.js","sourceRoot":"","sources":["../../src/core/verify.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -8,6 +8,8 @@ export * from './core/audit-events.js';
8
8
  export * from './core/repository.js';
9
9
  export * from './core/usability.js';
10
10
  export * from './core/invariants.js';
11
+ export * from './core/verify.js';
12
+ export * from './core/spec-sections.js';
11
13
  export * from './core/integrity.js';
12
14
  export * from './core/integrity-exemptions.js';
13
15
  export * from './core/remediation.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,iBAAiB,CAAC;AAChC,cAAc,uBAAuB,CAAC;AACtC,cAAc,kBAAkB,CAAC;AACjC,cAAc,oBAAoB,CAAC;AACnC,cAAc,mBAAmB,CAAC;AAClC,cAAc,kBAAkB,CAAC;AACjC,cAAc,wBAAwB,CAAC;AACvC,cAAc,sBAAsB,CAAC;AACrC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,qBAAqB,CAAC;AACpC,cAAc,gCAAgC,CAAC;AAC/C,cAAc,uBAAuB,CAAC;AACtC,cAAc,qBAAqB,CAAC;AACpC,cAAc,0BAA0B,CAAC;AACzC,cAAc,uBAAuB,CAAC;AACtC,cAAc,mBAAmB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,iBAAiB,CAAC;AAChC,cAAc,uBAAuB,CAAC;AACtC,cAAc,kBAAkB,CAAC;AACjC,cAAc,oBAAoB,CAAC;AACnC,cAAc,mBAAmB,CAAC;AAClC,cAAc,kBAAkB,CAAC;AACjC,cAAc,wBAAwB,CAAC;AACvC,cAAc,sBAAsB,CAAC;AACrC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,kBAAkB,CAAC;AACjC,cAAc,yBAAyB,CAAC;AACxC,cAAc,qBAAqB,CAAC;AACpC,cAAc,gCAAgC,CAAC;AAC/C,cAAc,uBAAuB,CAAC;AACtC,cAAc,qBAAqB,CAAC;AACpC,cAAc,0BAA0B,CAAC;AACzC,cAAc,uBAAuB,CAAC;AACtC,cAAc,mBAAmB,CAAC"}
package/dist/index.js CHANGED
@@ -9,6 +9,8 @@ export * from './core/audit-events.js';
9
9
  export * from './core/repository.js';
10
10
  export * from './core/usability.js';
11
11
  export * from './core/invariants.js';
12
+ export * from './core/verify.js';
13
+ export * from './core/spec-sections.js';
12
14
  export * from './core/integrity.js';
13
15
  export * from './core/integrity-exemptions.js';
14
16
  export * from './core/remediation.js';
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,yBAAyB;AACzB,cAAc,iBAAiB,CAAC;AAChC,cAAc,uBAAuB,CAAC;AACtC,cAAc,kBAAkB,CAAC;AACjC,cAAc,oBAAoB,CAAC;AACnC,cAAc,mBAAmB,CAAC;AAClC,cAAc,kBAAkB,CAAC;AACjC,cAAc,wBAAwB,CAAC;AACvC,cAAc,sBAAsB,CAAC;AACrC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,qBAAqB,CAAC;AACpC,cAAc,gCAAgC,CAAC;AAC/C,cAAc,uBAAuB,CAAC;AACtC,cAAc,qBAAqB,CAAC;AACpC,cAAc,0BAA0B,CAAC;AACzC,cAAc,uBAAuB,CAAC;AACtC,cAAc,mBAAmB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,yBAAyB;AACzB,cAAc,iBAAiB,CAAC;AAChC,cAAc,uBAAuB,CAAC;AACtC,cAAc,kBAAkB,CAAC;AACjC,cAAc,oBAAoB,CAAC;AACnC,cAAc,mBAAmB,CAAC;AAClC,cAAc,kBAAkB,CAAC;AACjC,cAAc,wBAAwB,CAAC;AACvC,cAAc,sBAAsB,CAAC;AACrC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,kBAAkB,CAAC;AACjC,cAAc,yBAAyB,CAAC;AACxC,cAAc,qBAAqB,CAAC;AACpC,cAAc,gCAAgC,CAAC;AAC/C,cAAc,uBAAuB,CAAC;AACtC,cAAc,qBAAqB,CAAC;AACpC,cAAc,0BAA0B,CAAC;AACzC,cAAc,uBAAuB,CAAC;AACtC,cAAc,mBAAmB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spec-manager",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "spec-manager: local-first spec-driven development platform. Pure markdown storage, no network, no MCP. Portable CLI + multi-agent instructions.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -14,6 +14,7 @@
14
14
  "types": "./dist/index.d.ts",
15
15
  "files": [
16
16
  "dist",
17
+ "scripts",
17
18
  "skill",
18
19
  "templates",
19
20
  "rules",
@@ -27,7 +28,8 @@
27
28
  "test": "vitest run",
28
29
  "test:watch": "vitest",
29
30
  "dev": "tsc --watch",
30
- "lint": "tsc --noEmit"
31
+ "lint": "tsc --noEmit",
32
+ "verify:installed-cli": "node scripts/verify-installed-cli.mjs"
31
33
  },
32
34
  "keywords": [
33
35
  "spec-driven",
@@ -64,12 +64,12 @@ added: 0.1.0
64
64
  applies_to: [L1_implemented]
65
65
  ---
66
66
 
67
- L1 spec 状态推进到 `implemented`(通常通过 L3 cascade)后,**必须**至少创建 1 张决策卡片:
67
+ L1 spec 状态推进到 `implemented`(通常通过 L3 cascade)后,**必须**至少存在 1 张 active 决策卡片;superseded 或 partial 卡片不满足 R18。为避免最后一个 Task 与 R18 形成循环依赖,正常流程应在 L1 已 `confirmed`、最后一个 Task 完成前预建卡片:
68
68
 
69
69
  ```bash
70
70
  spec-manager decision create <L1 code> --topic <topic> --what "..." --why "..." --criteria AC-1,AC-2
71
71
  ```
72
72
 
73
- **执行机制**:`task complete` 在 cascade 到 L1 implemented 后自动检查决策卡片存在性,缺失则抛出 R18 错误拒绝完成。`--force` 可跳过检查(用于历史补建场景)。R18 已纳入合规基线(`audit show` 检查)。
73
+ **执行机制**:`decision create` 允许关联 `confirmed` 或 `implemented` L1,拒绝 draft L1。`task complete` 在 cascade 到 L1 implemented 后自动检查 active 决策卡片,缺失则抛出 R18 错误并回滚完成事务。异常或历史恢复只能使用独立的 `--skip-r18`、`--skip-verification`、`--skip-verify`,且必须提供 `--reason`;旧 `--force` 被拒绝。R18 已纳入合规基线(`audit show` 检查)。
74
74
 
75
75
  新 L1 创建前(PRE-WRITE Q4)必须 `spec-manager decision list --topic <topic>` 查询历史决策。
@@ -0,0 +1,63 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { execFileSync } from 'node:child_process';
4
+ import { createHash } from 'node:crypto';
5
+ import { existsSync, readFileSync, readdirSync, realpathSync, statSync } from 'node:fs';
6
+ import { dirname, join, relative, resolve } from 'node:path';
7
+ import { fileURLToPath } from 'node:url';
8
+
9
+ const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
10
+ const installedRootOverride = process.env.SPEC_MANAGER_INSTALLED_ROOT;
11
+ const installedBin = installedRootOverride ? null : findInstalledBin();
12
+ const installedRoot = installedRootOverride ? resolve(installedRootOverride) : findPackageRoot(realpathSync(installedBin));
13
+ const localDist = join(repoRoot, 'dist');
14
+ const installedDist = join(installedRoot, 'dist');
15
+ const localFiles = listFiles(localDist);
16
+ const installedFiles = existsSync(installedDist) ? listFiles(installedDist) : [];
17
+ const allFiles = [...new Set([...localFiles, ...installedFiles])].sort();
18
+ const mismatches = allFiles.filter((file) => {
19
+ const local = join(localDist, file);
20
+ const installed = join(installedDist, file);
21
+ return !existsSync(local) || !existsSync(installed) || digest(local) !== digest(installed);
22
+ });
23
+
24
+ if (mismatches.length > 0) {
25
+ console.error(`INSTALLED_CLI_DRIFT: ${installedBin ?? installedRoot} differs from the current build`);
26
+ for (const file of mismatches) console.error(` - ${file}`);
27
+ process.exit(1);
28
+ }
29
+
30
+ console.log(`Installed CLI matches current build: ${installedBin ?? installedRoot}`);
31
+
32
+ function findInstalledBin() {
33
+ try {
34
+ return execFileSync('sh', ['-c', 'command -v spec-manager'], { encoding: 'utf8' }).trim();
35
+ } catch {
36
+ console.error('INSTALLED_CLI_MISSING: spec-manager is not available on PATH');
37
+ process.exit(1);
38
+ }
39
+ }
40
+
41
+ function digest(file) {
42
+ return createHash('sha256').update(readFileSync(file)).digest('hex');
43
+ }
44
+
45
+ function listFiles(root, base = root) {
46
+ const files = [];
47
+ for (const entry of readdirSync(root, { withFileTypes: true })) {
48
+ const path = join(root, entry.name);
49
+ if (entry.isDirectory()) files.push(...listFiles(path, base));
50
+ else if (entry.isFile() && statSync(path).isFile()) files.push(relative(base, path));
51
+ }
52
+ return files.sort();
53
+ }
54
+
55
+ function findPackageRoot(start) {
56
+ let current = dirname(start);
57
+ while (dirname(current) !== current) {
58
+ if (existsSync(join(current, 'package.json'))) return current;
59
+ current = dirname(current);
60
+ }
61
+ console.error(`INSTALLED_CLI_PACKAGE_MISSING: cannot locate package root for ${start}`);
62
+ process.exit(1);
63
+ }
@@ -63,17 +63,21 @@
63
63
  - step_report 必须在工作完成后才报(R15)
64
64
  > ☑ **R5** — `task complete` 时仍有 pending/running 步会拒绝并报错
65
65
  > ☑ **R15** — outputJson 缺 summary 时 warning 落 warn 数组
66
- 12. `spec-manager task complete <task-id>`
67
- > 完成后看 `cascadedL1Specs`,对每个 L1 用 `decision list --doc-code <L1>` 查是否已建决策卡
66
+ 12. 最后一个子 Task 完成前,对即将级联的 confirmed L1 检查并预建 R18 决策卡片:
67
+ ```bash
68
+ spec-manager decision list --doc-code <L1-code>
69
+ spec-manager decision create <L1-code> --topic <topic> --what "..." --why "..."
70
+ ```
71
+ 13. `spec-manager task complete <task-id>`
68
72
  > ☑ **R6** — 流程规则:确认 cascade 后再标 implemented
69
- > ☑ **R18** — L1 cascade 到 implemented 后自动校验决策卡片,缺失则拒绝完成
70
- 13. 若仍 frozen,手动 `spec-manager spec implement <L3 code>`
71
- 14. R18 决策卡片:task complete 会自动检查。若因历史原因缺少决策卡,用 `--force` 跳过检查后补建:
73
+ > ☑ **R18** — L1 cascade 到 implemented 后自动校验预建决策卡片,缺失则拒绝完成并回滚
74
+ 14. 若仍 frozen,手动 `spec-manager spec implement <L3 code>`
75
+ 15. 异常或历史恢复必须使用独立跳过参数并提供原因,跳过 R18 后必须立即补建 active 决策卡:
72
76
  ```bash
73
- spec-manager task complete <task-id> --force # 跳过 R18
74
- spec-manager decision create --doc-code <L1-code> --topic <topic> --what "..." --why "..."
77
+ spec-manager task complete <task-id> --skip-r18 --reason "历史仓库恢复"
78
+ spec-manager decision create <L1-code> --topic <topic> --what "..." --why "..."
75
79
  ```
76
- 15. 规则审计合规检查:
80
+ 16. 规则审计合规检查:
77
81
  ```bash
78
82
  spec-manager audit show
79
83
  ```