@dommaker/harness 1.9.0 → 1.11.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.
- package/CHANGELOG.md +17 -0
- package/README.md +9 -12
- package/dist/cli/commands/check.d.ts +3 -3
- package/dist/cli/commands/check.d.ts.map +1 -1
- package/dist/cli/commands/check.js +41 -78
- package/dist/cli/commands/check.js.map +1 -1
- package/dist/cli/commands/constraints-report.d.ts +2 -10
- package/dist/cli/commands/constraints-report.d.ts.map +1 -1
- package/dist/cli/commands/constraints-report.js +4 -57
- package/dist/cli/commands/constraints-report.js.map +1 -1
- package/dist/cli/commands/constraints-retire.d.ts +9 -23
- package/dist/cli/commands/constraints-retire.d.ts.map +1 -1
- package/dist/cli/commands/constraints-retire.js +39 -121
- package/dist/cli/commands/constraints-retire.js.map +1 -1
- package/dist/cli/commands/constraints.d.ts +3 -8
- package/dist/cli/commands/constraints.d.ts.map +1 -1
- package/dist/cli/commands/constraints.js +8 -23
- package/dist/cli/commands/constraints.js.map +1 -1
- package/dist/cli/commands/definitions.d.ts.map +1 -1
- package/dist/cli/commands/definitions.js +7 -12
- package/dist/cli/commands/definitions.js.map +1 -1
- package/dist/cli/commands/init.d.ts +0 -32
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +7 -149
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/report.d.ts.map +1 -1
- package/dist/cli/commands/report.js +14 -17
- package/dist/cli/commands/report.js.map +1 -1
- package/dist/cli/commands/scaffold-templates.d.ts +1 -3
- package/dist/cli/commands/scaffold-templates.d.ts.map +1 -1
- package/dist/cli/commands/scaffold-templates.js +2 -53
- package/dist/cli/commands/scaffold-templates.js.map +1 -1
- package/dist/cli/commands/scaffold.d.ts +0 -2
- package/dist/cli/commands/scaffold.d.ts.map +1 -1
- package/dist/cli/commands/scaffold.js +0 -10
- package/dist/cli/commands/scaffold.js.map +1 -1
- package/dist/cli/commands/status.js +10 -10
- package/dist/cli/commands/status.js.map +1 -1
- package/dist/cli/commands/sync-docs/agents-syncer.d.ts +0 -2
- package/dist/cli/commands/sync-docs/agents-syncer.d.ts.map +1 -1
- package/dist/cli/commands/sync-docs/agents-syncer.js +4 -23
- package/dist/cli/commands/sync-docs/agents-syncer.js.map +1 -1
- package/dist/core/constraints/capabilities-parser.d.ts.map +1 -1
- package/dist/core/constraints/capabilities-parser.js +16 -4
- package/dist/core/constraints/capabilities-parser.js.map +1 -1
- package/dist/core/constraints/checker.d.ts +15 -24
- package/dist/core/constraints/checker.d.ts.map +1 -1
- package/dist/core/constraints/checker.js +33 -72
- package/dist/core/constraints/checker.js.map +1 -1
- package/dist/core/constraints/checkers/docs-freshness.js +1 -1
- package/dist/core/constraints/checkers/docs-freshness.js.map +1 -1
- package/dist/core/constraints/checkers/governance-presence.d.ts +2 -1
- package/dist/core/constraints/checkers/governance-presence.d.ts.map +1 -1
- package/dist/core/constraints/checkers/governance-presence.js +4 -3
- package/dist/core/constraints/checkers/governance-presence.js.map +1 -1
- package/dist/core/constraints/checkers/index.d.ts +1 -1
- package/dist/core/constraints/checkers/index.d.ts.map +1 -1
- package/dist/core/constraints/checkers/index.js +4 -8
- package/dist/core/constraints/checkers/index.js.map +1 -1
- package/dist/core/constraints/checkers/iron-flags.d.ts +3 -4
- package/dist/core/constraints/checkers/iron-flags.d.ts.map +1 -1
- package/dist/core/constraints/checkers/iron-flags.js +4 -5
- package/dist/core/constraints/checkers/iron-flags.js.map +1 -1
- package/dist/core/constraints/checkers/no-test-simplification.js +1 -1
- package/dist/core/constraints/checkers/no-test-simplification.js.map +1 -1
- package/dist/core/constraints/checkers/types.d.ts +1 -1
- package/dist/core/constraints/checkers/types.js +1 -1
- package/dist/core/constraints/definitions/guidelines.d.ts +4 -4
- package/dist/core/constraints/definitions/guidelines.d.ts.map +1 -1
- package/dist/core/constraints/definitions/guidelines.js +9 -25
- package/dist/core/constraints/definitions/guidelines.js.map +1 -1
- package/dist/core/constraints/definitions/iron-laws.d.ts +4 -4
- package/dist/core/constraints/definitions/iron-laws.d.ts.map +1 -1
- package/dist/core/constraints/definitions/iron-laws.js +10 -81
- package/dist/core/constraints/definitions/iron-laws.js.map +1 -1
- package/dist/core/constraints/definitions.d.ts +7 -9
- package/dist/core/constraints/definitions.d.ts.map +1 -1
- package/dist/core/constraints/definitions.js +13 -16
- package/dist/core/constraints/definitions.js.map +1 -1
- package/dist/core/constraints/index.d.ts +2 -5
- package/dist/core/constraints/index.d.ts.map +1 -1
- package/dist/core/constraints/index.js +3 -13
- package/dist/core/constraints/index.js.map +1 -1
- package/dist/core/constraints/injection-writer.d.ts +10 -38
- package/dist/core/constraints/injection-writer.d.ts.map +1 -1
- package/dist/core/constraints/injection-writer.js +17 -54
- package/dist/core/constraints/injection-writer.js.map +1 -1
- package/dist/core/constraints/run-env.d.ts +1 -10
- package/dist/core/constraints/run-env.d.ts.map +1 -1
- package/dist/core/constraints/run-env.js +1 -22
- package/dist/core/constraints/run-env.js.map +1 -1
- package/dist/core/constraints/usage-report.d.ts +6 -8
- package/dist/core/constraints/usage-report.d.ts.map +1 -1
- package/dist/core/constraints/usage-report.js +6 -11
- package/dist/core/constraints/usage-report.js.map +1 -1
- package/dist/core/effective-constraints.d.ts +12 -22
- package/dist/core/effective-constraints.d.ts.map +1 -1
- package/dist/core/effective-constraints.js +13 -30
- package/dist/core/effective-constraints.js.map +1 -1
- package/dist/core/index.d.ts +2 -2
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +3 -9
- package/dist/core/index.js.map +1 -1
- package/dist/core/project-config-loader.d.ts +4 -42
- package/dist/core/project-config-loader.d.ts.map +1 -1
- package/dist/core/project-config-loader.js +16 -125
- package/dist/core/project-config-loader.js.map +1 -1
- package/dist/core/validators/passes-gate.d.ts +2 -2
- package/dist/core/validators/passes-gate.js +6 -6
- package/dist/core/validators/passes-gate.js.map +1 -1
- package/dist/index.d.ts +9 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +21 -18
- package/dist/index.js.map +1 -1
- package/dist/knowledge/lint.d.ts +17 -0
- package/dist/knowledge/lint.d.ts.map +1 -1
- package/dist/knowledge/lint.js +52 -0
- package/dist/knowledge/lint.js.map +1 -1
- package/dist/knowledge/store.d.ts +7 -0
- package/dist/knowledge/store.d.ts.map +1 -1
- package/dist/knowledge/store.js +48 -13
- package/dist/knowledge/store.js.map +1 -1
- package/dist/knowledge/types.d.ts +5 -1
- package/dist/knowledge/types.d.ts.map +1 -1
- package/dist/knowledge/types.js +5 -1
- package/dist/knowledge/types.js.map +1 -1
- package/dist/monitoring/trace-analyzer.d.ts.map +1 -1
- package/dist/monitoring/trace-analyzer.js +11 -12
- package/dist/monitoring/trace-analyzer.js.map +1 -1
- package/dist/monitoring/traces.d.ts +4 -4
- package/dist/monitoring/traces.d.ts.map +1 -1
- package/dist/monitoring/traces.js +8 -8
- package/dist/monitoring/traces.js.map +1 -1
- package/dist/presets/standard.d.ts +8 -10
- package/dist/presets/standard.d.ts.map +1 -1
- package/dist/presets/standard.js +10 -16
- package/dist/presets/standard.js.map +1 -1
- package/dist/types/constraint.d.ts +24 -35
- package/dist/types/constraint.d.ts.map +1 -1
- package/dist/types/constraint.js +8 -7
- package/dist/types/constraint.js.map +1 -1
- package/dist/types/passes-gate.d.ts +5 -5
- package/dist/types/passes-gate.d.ts.map +1 -1
- package/dist/types/project-config.d.ts +8 -66
- package/dist/types/project-config.d.ts.map +1 -1
- package/dist/types/project-config.js +3 -2
- package/dist/types/project-config.js.map +1 -1
- package/dist/types/trace.d.ts +8 -10
- package/dist/types/trace.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/CONTEXT.md +2 -2
- package/src/__tests__/checker-extra.test.ts +37 -61
- package/src/__tests__/checker.test.ts +10 -115
- package/src/__tests__/effective-constraints.test.ts +18 -83
- package/src/__tests__/iron-laws.test.ts +71 -90
- package/src/__tests__/passes-gate-check.test.ts +2 -2
- package/src/__tests__/project-config-loader-extra.test.ts +22 -123
- package/src/__tests__/project-config-loader.test.ts +5 -33
- package/src/__tests__/public-exports.test.ts +8 -7
- package/src/__tests__/public-type-surface.test.ts +7 -5
- package/src/__tests__/public-value-surface.test.ts +1 -7
- package/src/__tests__/skip-semantics.test.ts +21 -37
- package/src/__tests__/trace-analyzer-extra.test.ts +14 -14
- package/src/__tests__/trace-analyzer.test.ts +21 -21
- package/src/__tests__/trace-collector.test.ts +22 -22
- package/src/__tests__/traces.test.ts +27 -28
- package/src/__tests__/usage-report-public-export.test.ts +21 -0
- package/src/cli/commands/CONTEXT.md +12 -12
- package/src/cli/commands/__tests__/check-read-count.test.ts +4 -15
- package/src/cli/commands/__tests__/check-skip-output.test.ts +14 -14
- package/src/cli/commands/__tests__/check.test.ts +14 -31
- package/src/cli/commands/__tests__/constraints-report-json-flag.test.ts +101 -0
- package/src/cli/commands/__tests__/constraints-report.test.ts +12 -80
- package/src/cli/commands/__tests__/constraints-retire.test.ts +42 -231
- package/src/cli/commands/__tests__/init-governance-io.test.ts +12 -58
- package/src/cli/commands/__tests__/init-ondisk.test.ts +13 -21
- package/src/cli/commands/__tests__/init.test.ts +1 -16
- package/src/cli/commands/__tests__/numeric-flag-assembly.test.ts +1 -1
- package/src/cli/commands/__tests__/passes-gate-dead-options.test.ts +1 -1
- package/src/cli/commands/__tests__/project-path-anchoring.test.ts +2 -2
- package/src/cli/commands/__tests__/registry.test.ts +1 -1
- package/src/cli/commands/__tests__/report-extra.test.ts +4 -4
- package/src/cli/commands/__tests__/report.test.ts +1 -1
- package/src/cli/commands/__tests__/scaffold.test.ts +4 -14
- package/src/cli/commands/__tests__/status-analysis-wiring.test.ts +3 -3
- package/src/cli/commands/__tests__/status-extra.test.ts +14 -14
- package/src/cli/commands/__tests__/status.test.ts +12 -12
- package/src/cli/commands/__tests__/sync-docs-agents.test.ts +1 -1
- package/src/cli/commands/check.ts +43 -82
- package/src/cli/commands/constraints-report.ts +4 -59
- package/src/cli/commands/constraints-retire.ts +49 -154
- package/src/cli/commands/constraints.ts +11 -26
- package/src/cli/commands/definitions.ts +7 -12
- package/src/cli/commands/init.ts +8 -179
- package/src/cli/commands/report.ts +19 -23
- package/src/cli/commands/scaffold-templates.ts +1 -53
- package/src/cli/commands/scaffold.ts +0 -11
- package/src/cli/commands/status.ts +10 -10
- package/src/cli/commands/sync-docs/agents-syncer.ts +4 -25
- package/src/core/CONTEXT.md +9 -9
- package/src/core/constraints/__tests__/capability-counts.test.ts +5 -5
- package/src/core/constraints/__tests__/collect-constraints.test.ts +13 -8
- package/src/core/constraints/__tests__/git-evidence.test.ts +4 -7
- package/src/core/constraints/__tests__/injection-writer.test.ts +8 -61
- package/src/core/constraints/__tests__/run-env.test.ts +5 -34
- package/src/core/constraints/__tests__/trace-injection.test.ts +1 -1
- package/src/core/constraints/capabilities-parser.ts +17 -5
- package/src/core/constraints/checker.ts +39 -88
- package/src/core/constraints/checkers/__tests__/check-outcome.test.ts +5 -8
- package/src/core/constraints/checkers/__tests__/governance-presence.test.ts +1 -53
- package/src/core/constraints/checkers/__tests__/iron-flags.test.ts +5 -25
- package/src/core/constraints/checkers/docs-freshness.ts +1 -1
- package/src/core/constraints/checkers/governance-presence.ts +4 -3
- package/src/core/constraints/checkers/index.ts +4 -10
- package/src/core/constraints/checkers/iron-flags.ts +3 -12
- package/src/core/constraints/checkers/no-test-simplification.ts +1 -1
- package/src/core/constraints/checkers/types.ts +1 -1
- package/src/core/constraints/definitions/guidelines.ts +8 -25
- package/src/core/constraints/definitions/iron-laws.ts +9 -82
- package/src/core/constraints/definitions.ts +14 -15
- package/src/core/constraints/index.ts +4 -19
- package/src/core/constraints/injection-writer.ts +16 -61
- package/src/core/constraints/run-env.ts +1 -33
- package/src/core/constraints/usage-report.ts +9 -16
- package/src/core/effective-constraints.ts +14 -45
- package/src/core/index.ts +3 -10
- package/src/core/project-config-loader.ts +20 -145
- package/src/core/validators/passes-gate.ts +6 -6
- package/src/failure/CONTEXT.md +1 -1
- package/src/hooks/__tests__/bootstrap.test.ts +3 -5
- package/src/index.ts +28 -18
- package/src/knowledge/CONTEXT.md +4 -3
- package/src/knowledge/__tests__/lint.test.ts +84 -0
- package/src/knowledge/__tests__/store.test.ts +70 -0
- package/src/knowledge/lint.ts +60 -1
- package/src/knowledge/store.ts +48 -11
- package/src/knowledge/types.ts +7 -1
- package/src/monitoring/CONTEXT.md +1 -1
- package/src/monitoring/__tests__/trace-file-anchoring.test.ts +1 -1
- package/src/monitoring/__tests__/traces.test.ts +22 -22
- package/src/monitoring/trace-analyzer.ts +12 -13
- package/src/monitoring/traces.ts +8 -8
- package/src/presets/__tests__/standard.test.ts +12 -16
- package/src/presets/standard.ts +14 -23
- package/src/types/__tests__/types.test.ts +3 -3
- package/src/types/constraint.ts +26 -40
- package/src/types/passes-gate.ts +5 -5
- package/src/types/project-config.ts +8 -77
- package/src/types/trace.ts +8 -11
- package/dist/cli/commands/posteval-plan.d.ts +0 -15
- package/dist/cli/commands/posteval-plan.d.ts.map +0 -1
- package/dist/cli/commands/posteval-plan.js +0 -86
- package/dist/cli/commands/posteval-plan.js.map +0 -1
- package/dist/core/constraints/agent-prompt-renderer.d.ts +0 -32
- package/dist/core/constraints/agent-prompt-renderer.d.ts.map +0 -1
- package/dist/core/constraints/agent-prompt-renderer.js +0 -55
- package/dist/core/constraints/agent-prompt-renderer.js.map +0 -1
- package/dist/core/constraints/checkers/no-bypass-checkpoint.d.ts +0 -6
- package/dist/core/constraints/checkers/no-bypass-checkpoint.d.ts.map +0 -1
- package/dist/core/constraints/checkers/no-bypass-checkpoint.js +0 -40
- package/dist/core/constraints/checkers/no-bypass-checkpoint.js.map +0 -1
- package/dist/core/constraints/definitions/prompts.d.ts +0 -12
- package/dist/core/constraints/definitions/prompts.d.ts.map +0 -1
- package/dist/core/constraints/definitions/prompts.js +0 -400
- package/dist/core/constraints/definitions/prompts.js.map +0 -1
- package/dist/core/constraints/injection-drift.d.ts +0 -61
- package/dist/core/constraints/injection-drift.d.ts.map +0 -1
- package/dist/core/constraints/injection-drift.js +0 -131
- package/dist/core/constraints/injection-drift.js.map +0 -1
- package/dist/core/constraints/injection-renderer.d.ts +0 -23
- package/dist/core/constraints/injection-renderer.d.ts.map +0 -1
- package/dist/core/constraints/injection-renderer.js +0 -44
- package/dist/core/constraints/injection-renderer.js.map +0 -1
- package/src/cli/commands/__tests__/check-drift.test.ts +0 -158
- package/src/cli/commands/__tests__/init-injection.test.ts +0 -466
- package/src/cli/commands/__tests__/posteval-plan.test.ts +0 -204
- package/src/cli/commands/posteval-plan.ts +0 -98
- package/src/core/constraints/__tests__/agent-prompt-renderer.test.ts +0 -129
- package/src/core/constraints/__tests__/injection-drift.test.ts +0 -247
- package/src/core/constraints/agent-prompt-renderer.ts +0 -67
- package/src/core/constraints/checkers/__tests__/no-bypass-checkpoint.test.ts +0 -76
- package/src/core/constraints/checkers/no-bypass-checkpoint.ts +0 -40
- package/src/core/constraints/definitions/prompts.ts +0 -414
- package/src/core/constraints/injection-drift.ts +0 -142
- package/src/core/constraints/injection-renderer.ts +0 -44
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# cli/commands/
|
|
2
2
|
|
|
3
3
|
## 职责
|
|
4
|
-
CLI 子命令实现:
|
|
4
|
+
CLI 子命令实现:20 个顶层命令 + constraints 治理子命令 report/retire,覆盖约束检查/门禁验证/文档同步/知识管理/失败诊断/发布等。
|
|
5
5
|
|
|
6
6
|
H5(#44)起:
|
|
7
7
|
- **命令定义即注册**:`definitions.ts` 的 `COMMAND_DEFINITIONS` 是命令形状(名称/别名/位置参数/选项/子命令/实现引用)的单一来源;bin/harness.js 遍历本表注册表驱动生成,不再手写 commander 命令块,不再有 index.ts barrel 两处同步(R6)
|
|
@@ -12,13 +12,13 @@ H5(#44)起:
|
|
|
12
12
|
- 命令契约(`src/cli/command-contract.ts`,上层目录)— `CommandResult` / `CommandKind` / `CommandIO` / `processIO` / `captureIO` / `lastJsonOutput` / `log` / `logError`
|
|
13
13
|
- 状态文件接缝(`src/cli/state-io.ts`,上层目录)— `.harness/.state.json` 读-改-写的唯一入口 `StateIO`(`read()`/`write()`)+ 状态类型 `HarnessState` + 缺省真实 fs 实现 `fileStateIO(projectPath)`;`check` / `status` 经可选 `stateIO` 参数注入(ADR-0026,harness#148)
|
|
14
14
|
- 门禁命令共享面(`src/cli/gate-command.ts`,上层目录)— `GateDecision → CommandResult` 的唯一映射 `gateCommandResult`,加 ✓/✗ 输出骨架 `reportGateDecision` 与出错横幅 `reportGateError`。同一句失败措辞此前抄在 6 个 handler 里(「`<id>` gate denied」×6 / 「`<id>` gate error」×4),门禁特有的指标行经 `onPass`/`onFail` 闭包传入(架构评审候选1)
|
|
15
|
-
- 脚手架落盘面(`src/cli/commands/scaffold.ts`)— 受管文件(managed file)三态判定 `writeManagedFile` / 一组落盘 `runPlan` +
|
|
15
|
+
- 脚手架落盘面(`src/cli/commands/scaffold.ts`)— 受管文件(managed file)三态判定 `writeManagedFile` / 一组落盘 `runPlan` + 8 个站点工厂(含对外文案;ADR-0029:custom-constraints.yml 示例站随 custom 约束面退役移除);模板正文住 `scaffold-templates.ts`,落盘注入面 `ScaffoldFileSystem` 可换内存替身(harness#132);CI 站点工厂 `harnessCheckCiFile` 带平台维度(github / gitlab,harness#143);两道 Git hook 工厂 `preCommitHookFile` / `prePushHookFile`(后者 harness#144,带可执行位)
|
|
16
16
|
- knowledge 投影面(`src/cli/commands/knowledge-view.ts`)— 11 个 knowledge 子操作的**display model** 渲染与出口:`emitKnowledgeView`(json/人读唯一分派 + 退出码)、`announce`(取数期进度行,`--json` 下静默)、`TONE_STYLES` 角色→样式单表、维度/规则 label 与 `toneForMaturity`/`toneForScore`/`toneForSeverity` 三张映射、`resolveKnowledgeBaseDir` + `openKnowledgeStore`(路径兜底与 store 构造单点)(harness#133)
|
|
17
|
-
- 各命令文件:check / validate / passes-gate / init / report / status / spec / sync-docs / knowledge / sdd / failure /
|
|
17
|
+
- 各命令文件:check / validate / passes-gate / init / report / status / spec / sync-docs / knowledge / sdd / failure / release / constraints / spec-baseline-check
|
|
18
18
|
- 6 门禁命令实现在 `acceptance` / `command` / `contract` / `performance` / `review` / `security`(其 CLI 元数据在 `src/gates/definitions.ts`,形状同为 `CommandDefinition`,ADR-0007);**判定一律穿过统一接口 `evaluate()`**,成败与措辞由共享面映射。`command` 只复用映射、保留自己的单行 ✓/✗ 输出(已对外的机器友好形状);`security audit` / `acceptance list` / `contract validate-schema` / `review status` / `command --list/--level` 是只展示不判断的子命令,直读报告面(`scan()` 等),不套骨架
|
|
19
19
|
- CLI 子命令 `command` 的两个分支共用同一台 gate 实例(#135/ADR-0024):默认分支经 `evaluate()` 取裁决、`--level` 经同一实例的 `getRiskLevel()` 取等级,命令侧不再引用模块级单例出口——「配置传进去却到不了判定」的那条路已封。`--strict` 旗帜随 `CommandGateConfig.strict`(零消费者、从不生效)一并删除
|
|
20
20
|
|
|
21
|
-
`constraints` 下挂治理子命令:`constraints report`(使用统计 + 退役候选诊断 +
|
|
21
|
+
`constraints` 下挂治理子命令:`constraints report`(使用统计 + 退役候选诊断 + 配置健康,`--export` 脱敏;JSON 输出走 `--json-output`——原名 `--json` 与父命令同名会被 commander 消费在父侧、JSON 分支不可达,E1 复盘修正 M3.1 换名,闸见 `__tests__/constraints-report-json-flag.test.ts`)、`constraints retire`(交互选择 + 人确认退役;带 id 直达需显式 `--yes`(#24 人确认闸门),无 `--yes` 报错 + 非零退出码且不落盘;落 config.yml retired 元数据 + KnowledgeStore 沉淀。ADR-0029:注入漂移小节、prompt 注入清单、custom 落点与治理注入段同步已随文本注入层关停移除)。
|
|
22
22
|
|
|
23
23
|
## 依赖关系
|
|
24
24
|
- 依赖 `src/core/constraints/` 约束引擎
|
|
@@ -41,7 +41,7 @@ H5(#44)起:
|
|
|
41
41
|
- 行为可检:`__tests__/project-path-anchoring.test.ts` 前提统一 **cwd ≠ projectPath**、不 mock 任何 IO,断言测试真在 B 执行、证据落 `B/.harness/evidence/`、acceptance 读 `B/tasks.yml`、`check --project-path` 的 trace 落 `B/.harness/logs/traces.log` 且 `status` 读得到它(站点 3,#139)。「根已传入却又取 cwd」这类静态看不出的漂移由它守,冻结表守的是不再新增站点
|
|
42
42
|
- **读到部分结果必须告知(harness#100)**:命令面以 `skip` 策略读 JSONL(traces.log / failures.log)时,坏行计数要到用户可见输出,**不允许默默丢弃**。共同约束:无损坏零噪声(计数为 0 不出行)、不改任何既有计数口径(`status` 的 `记录数` 仍是原始非空行数,逐字节不变)、计数一律取自 `src/utils/jsonl` 读链返回的 `skippedLines`(经报告入口透传或在读点就地取用),**禁止**用「原始行数 − 统计条数」反推(窗过滤掉的合法行与坏行分不开)。三处落点各自对得上一行代码:
|
|
43
43
|
- `status`:告知**走 stderr**(`logError(io, …)`),因为 stdout 是报告体、其字节已被 `记录数` 那一行定死,挤进去等于改输出
|
|
44
|
-
- `constraints report`:提示属于**报告体内的降级位**,与既有的 `traceFileExists` 说明同在 stdout;结构化面另两份——`--json` 的 `skippedLines` 字段、`--export` markdown 的警示行(只报条数,不报路径与坏行内容)
|
|
44
|
+
- `constraints report`:提示属于**报告体内的降级位**,与既有的 `traceFileExists` 说明同在 stdout;结构化面另两份——`--json-output` 的 `skippedLines` 字段、`--export` markdown 的警示行(只报条数,不报路径与坏行内容)
|
|
45
45
|
- `constraints retire`:两条路径都**走 stderr**——交互模式在候选列表前一行(沿用本函数既有的 console 例外,`console.error`),`--yes` 直达分支在落盘前 `logError(io, …)`(`retireConstraint` 本身按契约「纯执行无交互」不打印)
|
|
46
46
|
- 原则正本与逐点去向见 `src/monitoring/CONTEXT.md`「约定」段;机器可检面是 `src/utils/__tests__/jsonl-skip-disposition.test.ts`(冻结全仓 skip 读点集合 + 要求每个读点记名声明 `计数去向:`)
|
|
47
47
|
- **一次命令运行内,同一份数据至多取一次(harness#146,#140 A 票;与 ADR-0023 同族,但不扩 `RunEnv` 公共面)**:`spec-baseline-check` 的三条验证路径(文件存在性 / 依赖 / 代码模式)一律从命令级共享索引 `createBaselineIndex(projectPath)` 取数,不再各自裸调 `fs`——全仓 `.ts`/`.js` 内容扫描一遍建成 `路径 → 内容` 表、`package.json` 读解析一次(**读取失败态同样入库**,不逐条重试)、同一落点的存在性探测只 stat 一次;逐条前置与每个关键词只在索引上重放判定。索引**懒建**(没有前置需要某类数据时该类读取一次都不发生)、**运行结束即弃**(不跨命令复用:本命令是独立命令而非 `harness check` 的子步骤,「同一口径取一次」命令内自足即够,#140 triage 裁决 5)。改前代价 = 前置条件数 × 关键词数 × 源文件数,改后与条数无关,判定/证据文案/stdout/退出码逐字不变。**驻留上界(harness#162)**:索引容量有双上限 `SOURCE_INDEX_MAX_ENTRIES = 5000` / `SOURCE_INDEX_MAX_BYTES = 64MB`——超任一上限即放弃驻留、**回落逐文件流式读**(每个关键词重扫一遍,读完即弃),峰值内存由 O(全仓源码) 回到 O(单文件),正常规模仓保留「一次遍历」收益;取数面相应由 `sourceContents(): Map` 改为 `countFilesContaining(keyword)`,容量策略全部收在索引内部。机器可检:`__tests__/spec-baseline-read-count.test.ts`(形状照 `check-read-count.test.ts`:`readFileSync`·只读 `openSync`·`existsSync`·`readdirSync` 记件 + 整张读取表与目录扫描表逐条冻结 + 「同一份数据的同一口径至多取一次」+ N×K 缩放不变式 + 懒建不扫 + 失败态入库)+ `__tests__/spec-baseline-index-cap.test.ts`(驻留上界闸:上限内每文件至多读一遍、超条目/字节上限回落流式且判定不变)
|
|
@@ -51,15 +51,15 @@ H5(#44)起:
|
|
|
51
51
|
|
|
52
52
|
## 注意事项
|
|
53
53
|
- 新增命令需同步更新 CLAUDE.md / CAPABILITIES.md / src/CONTEXT.md
|
|
54
|
-
- **scaffold 三态模型(harness#132 已落地)**:init/validate
|
|
55
|
-
-
|
|
56
|
-
- 可检面:`__tests__/scaffold.test.ts`(注入内存 fs 替身,零真实文件系统测全三态 +
|
|
54
|
+
- **scaffold 三态模型(harness#132 已落地)**:init/validate 的「目标在不在场→写或告知」判定(站点数历经 8→9→8:#132 收口时 8 处,harness#144 加 pre-push 成 9,ADR-0029 撤 custom-constraints.yml 示例站回 8 = init 6 + validate 2)收口为 scaffold 模块。术语:**managed file**(受管文件)= harness 模板拥有正本、用户可持有的落盘文件;三态 = `created`(不在场→落盘)/ `exists`(在场→告知跳过,**不细分内容是否等于模板**,内容比较是独立特性)/ `manual`(在场→打印片段请用户手工合并)。纪律:scaffold 不持有覆盖/跳过策略(`--no-git-hooks`/`--no-github-actions` 由命令层翻成 plan 列表,scaffold 只见 plan);模板留代码内住 `scaffold-templates.ts`,不落 `templates/`(其无运行时消费者、integrity 清单不覆盖);**打印给用户的片段必须是落盘正文的一部分**(GH Actions 站点冲突分支打印完整 workflow 全文而非 job 片段,#103 判据);`--print-snippets` 的片段视图同判据且**由正本裁剪派生**(harness#153:`GITHUB_ACTIONS_SNIPPET` = `HARNESS_CHECK_WORKFLOW` 的 `jobs:` 段之后,形状与 GitLab 侧「打印的是落盘正文的尾巴」对仗。它曾是手抄的第二份文本,比落盘正本少 `validate` / `passes-gate` 两道门禁——照抄的用户拿到比同版本 `harness init` 弱一半的 CI;引导语随之改准为「以下正文取自 harness-check.yml 的 job 段」,`--print-snippets` 旗帜本身保留);gitlab 形同判据由 harness#157 补齐——`printSnippets` 收 `governanceLevel`、打 `renderGitLabCiJobs(governanceLevel)`,与落盘 / 冲突分支同一个正本函数(此前打无 level 的 `GITLAB_CI_SNIPPET`,`-g` 档照抄的用户静默少拿治理与 docs 新鲜度两个任务);CLAUDE.md/AGENTS.md 标记化幂等写(读-改-写形状)不纳入 scaffold;死选项 `-t/--type` 已随本票删除。GitLab CI 接线决议见下条;pre-push 站点已随 harness#144 落地,见「本地防线两道」条
|
|
55
|
+
- 站点的落点(#132 收口时 8 处,pre-push 是 harness#144 加的第 9 处,ADR-0029 撤 custom-constraints.yml 示例站后回到 8 处):`scaffold.ts` 存 8 个 `ManagedFile` 工厂(目标路径 + 三态文案 + 冲突片段),`init.ts` 只留「前置告知(无 .git / 目录不在场 / 已有 workflow 覆盖治理命令)+ runPlan」;原住 `validate.ts` 的 `createExampleCheckpoint`/`createExampleResolutions` 随工厂迁入 scaffold,`init.ts` 对 `validate` 的反向 import 因此消失,`validate.ts` 反向消费 `scaffold-templates` 的检查点文件路径约定。新增受管文件 = 一个工厂 + 一条 plan。**#132 口径外的第 9 处同形写手**(`sync-docs/context-syncer.ts:createContextMd` 自带一份 CONTEXT.md 正文、与正本已漂移)已于 harness#150 归位——现走 `contextDocFile` + `runPlan`,与 init 共用同一模板正本(`scaffold-templates.ts:renderContextDoc`)
|
|
56
|
+
- 可检面:`__tests__/scaffold.test.ts`(注入内存 fs 替身,零真实文件系统测全三态 + 8 站点文案逐字冻结 + 同源闸及其反证探针(两道 hook 各一条)+ 片段视图的「是正本裁出来的一段」包含闸)、`__tests__/init-ondisk.test.ts`(临时目录真跑 init:第一遍逐字节断言落盘内容并对齐改造前的整屏输出(#149 起这份整屏多出治理段 writer 的一行落盘提示——那行本就一直在真机上,只是此前 io 被丢、不进捕获面),第二遍断言 8 站点第三态与「打印即磁盘正文」;另有「`--print-snippets` 的 GitHub Actions 片段视图」两道闸——同源闸对 `harness-check.yml` 的字节级冻结基线、steps 集合闸按 YAML 解析枚举 job 的 `run:` 命令;harness#157 起「`--print-snippets` 的 GitLab CI 片段视图」四道闸——`-g standard` 同源闸(打印正文逐字等于 `GITLAB_GOVERNED` 基线的任务段 + 与真落盘 `.gitlab-ci.yml` 按 YAML 解析比对 harness-* 任务集合,正本删一个任务即红)、script 集合闸、不传 `-g` 单任务回归、`-g` 非法值 usage-error 零打印)
|
|
57
57
|
- 同族不同面:#150(已落地)是同一份 CONTEXT.md 模板被 `init` 与 `sync-docs` 两个命令各抄一份(`sync-docs/context-syncer.ts:createContextMd` 自带一份、与正本 `renderContextDoc` 已漂移),本条 #153 是同一命令的两个出口(打印 / 落盘)各抄一份——判据同一个(#103),落点不同,两票不并。#150 收口后 `createContextMd` 走 `contextDocFile` + `runPlan`(含 io / 注入 fs,正常只落 `created`),可检面 `__tests__/context-syncer.test.ts`(对撞闸:sync-docs 落点与 init 站点的骨架逐字相同;正本并入两行引导 + 注释形占位钉住;present→exists 不覆盖;#158 补骨架正文逐字冻结字面量——对撞闸只钉「两侧同源」,正本被改由冻结字面量撞红)与 `__tests__/init-ondisk.test.ts`(#158 起 `src/CONTEXT.md` 落盘从 `toContain` 升为 `CONTEXT_MD_BYTES` 字节级基线,同 `PRE_PUSH_HOOK_BYTES` 手法)
|
|
58
58
|
- **CI 平台维度(harness#143 已落地)**:init 的服务端 CI 接线带平台维度。术语:**CI 平台(ci platform)** = 服务端门禁的接线目标(`github` / `gitlab` / `none`,`none` = 不接线,即旧 `--no-github-actions`),一次平台选择贯通三个面(harness-check 站点、治理 CI 面、`--print-snippets` 的 CI 片段)。决议 ①–⑥ 的落地形状:① 显式 `--ci <github|gitlab|none>`、缺省 `github`、**不做 remote URL 自动检测**(init 常跑在首次 push 前,检测错是静默写错文件);② 最小参数化——CI 站点工厂 `harnessCheckCiFile(projectPath, platform, existingCiWorkflows, governanceLevel?)` 收平台参数,GitLab 正文住 `scaffold-templates.ts`(`GITLAB_CI_SNIPPET` / `renderGitLabCiJobs` / `renderGitLabCiFile`),`renderGovernanceWorkflow(level, platform)` 同步参数化,不建注册表抽象;③ 解析链 = flag > `.harness/config.yml` 的 `ci.platform` > `github`(读点 `readConfiguredCiPlatform`,缺失/脏值按未配置),**只有非默认平台才写 `ci.platform`**——github 不写键,而 init 每次都重写 config.yml,故「不写」即等于清除,旧配置零迁移、validate 不报错;④ `--no-github-actions` 保留为 `--ci none` 别名并打 deprecation 警告,与显式 `--ci <非 none>` 同时出现判 `usage-error` 且完全不落盘(不猜意图);⑤ `.gitlab-ci.yml` 三态:不在场 → `created`、在场 → `manual`(打印 harness 任务正文,它是落盘全文的尾巴 → #103 判据成立),GH 侧冲突判定逐字不变;⑥ GitLab 模板与 GH 对仗(同三条命令 + `image: node:20` + `stage: test` + `rules:` 触发 MR 与 main/master push + 无缓存),治理档 `sync-docs --check` 的非阻断对应 `allow_failure: true`
|
|
59
59
|
- 平台维度带来的两处形状差(GH 侧行为逐字节不变):**一个平台一份 CI 文件**——GitLab 没有「第二个 CI 文件位」,`-g` 档的治理任务并入同一份 `.gitlab-ci.yml` 正文,gitlab 形下 `governanceWorkflowFile` 无站点;**`none` = 不接线任何服务端 CI、不创建任何 CI 文件,治理 CI 面随之不建**(harness#156 裁决 F1,推翻 #143「`none` 只摘 harness-check 站点、`-g` 档照建 GH 治理 workflow」的有意保留——旧边界让 `config.yml` 写 `ci.platform: none` 而磁盘上躺着一份 GitHub Actions 文件,状态自相矛盾);治理文档面(CHANGELOG、约束段、CONTEXT.md)由 `-g` 独立决定,`none` 下照常生成。`--print-snippets` 照出 GH 片段(非落盘面,#156 未动)。切回 `github` 时 init 不删既有的 `.gitlab-ci.yml`——scaffold 只有「落盘 / 告知」两种动作,删除不是它的能力
|
|
60
60
|
- `-g` 值域校验在命令入口(harness#156 裁决 F2,与 #152 脏输入 fail-loud 同判据):合法值域 = `GOVERNANCE_PRESETS` 键集合(入口校验与装配共用同一份表),非法值判 `usage-error` 退 1、先于任何落盘,github / gitlab / none 三平台同判(旧行为:gitlab 侧非法值透传模板落三个任务、github 侧查表 miss 静默不建——同一输入两种后果)。隶属判定用 `Object.hasOwn` 而非 `in`(harness#164):`in` 走原型链,`-g constructor`/`toString` 曾穿透校验、把 `Object` 构造函数写进 `configData` 并在 `yaml.dump` 抛 `YAMLException`
|
|
61
61
|
- 可检面:`__tests__/scaffold.test.ts`「CI 站点的平台维度」+ 同源闸两形覆盖、`__tests__/init-ondisk.test.ts`「init --ci 平台维度」(`.gitlab-ci.yml` 逐字节 / 第二趟 manual / 治理并入 / 持久化回读 / 别名与冲突与非法值)与「init --ci × -g 组合边界(harness#156)」(`--ci none -g standard` 落盘文件集合 + config.yml 两投影逐字冻结、`-g` 非法值三平台一致性钉、合法值回归)
|
|
62
|
-
- **本地防线两道(harness#144 已落地)**:术语:**本地防线** = init 落的两个 Git hook 站点,分工是设计而非冗余——**pre-commit** 查暂存的(`check --staged` + plan 覆盖,增量、快反馈),**pre-push**
|
|
62
|
+
- **本地防线两道(harness#144 已落地)**:术语:**本地防线** = init 落的两个 Git hook 站点,分工是设计而非冗余——**pre-commit** 查暂存的(`check --staged` + plan 覆盖,增量、快反馈),**pre-push** 查整仓的(`prePushHookFile` → `.git/hooks/pre-push`,`mode 0o755`,全量 `harness check` 不带 `--staged` + `harness validate`,任一失败 `exit 1` 挡住 push),兜的是 `git commit --no-verify` 绕出去的内容。三道决定按 #132 grilling 定论钉死:① **不做增量**——不解析 pre-push 从 stdin 收到的 `<local_ref> <local_oid> <remote_ref> <remote_oid>` 清单,无论推什么(含删远端分支这种 stdin 空清单的场景)整仓过一遍,钩子内子进程一律 `< /dev/null`、不让它吃掉 ref 清单;② **不内建逃生机制**——无环境变量开关、无超时、无「慢则降级」,唯一口子是 git 原生 `git push --no-verify`;③ **定位不变**——本地 hook = 自检与提醒,服务端 CI(见「CI 平台维度」条)才是门禁,pre-push 不替代它。`--no-git-hooks` 一并覆盖本站点(策略仍在命令层翻成 plan,scaffold 不持策略、不新增跳过旗帜);`--print-snippets` 与冲突分支打印的片段 = 落盘正文去 shebang 头(#103 判据);init 收尾提示与本站点引导语写明「查暂存的 / 查整仓的,重复是设计使然」。仓库自身的 `bin/install-precommit-hook.sh`(`npm run hooks:install`)仍是 pre-commit 单道,本票不动它
|
|
63
63
|
- **knowledge view 收口模型(harness#133 已落地)**:knowledge 11 个子操作的「取数 + json 投影 + 人读排版 + 退出码」四件事拆开——命令模块只产 `{ data, human() }`(`data` 是 `--json` 正文的唯一正本,11 个 `knowledgeXxxView` 具名导出即投影本体),`knowledge-view.ts` 收口排版渲染、上色、label 映射、json/人读分派与退出码、路径解析与 store 构造。术语:**display model** = `human()` 返回的结构化人读视图 `{ sections: [{ title, rows: [{ cells }] }] }`,一格(cell)三选一:`label`(纯文案)/ `field`(点分路径声明它投影自 json 面哪个字段,数组下标与 `.length` 合法)/ `derived`(json 面没有的派生量,须在测试豁免表登记理由)。空行是显式的一行(`blankLine()`),不再靠 `\n` 前后缀藏排版。颜色只经 `DisplayTone` 角色查 `TONE_STYLES` 一张表,命令侧零 `chalk` 调用。可检面:`__tests__/knowledge-view.test.ts`(四道闸:源形状 / 11·11 json 字段清单冻结 / 两投影一致性 + 派生量双向对撞 / 满态与空态人读逐行冻结,基线取自改造前实现实测捕获)+ `__tests__/knowledge-tone-mapping.test.ts`(#151 加:tone 映射的纯函数级冻结,判据见下两条)。
|
|
64
64
|
- **冻结类测试的两条判据(#133 收尾须知自报第一条,#151 补完其推论)**:
|
|
65
65
|
- **fixture 走不到的分支须逐个建形,不共用一个空根**。闸 4b 的 `emptyRoot()` 是裸 `mkdtemp` 目录,天然没有 `.harness/knowledge-docs/`,于是 `syncRagRows` 的两个空态分支只有第一个(`!docsPresent`)进过基线,第二个(目录在场、无 `.md`)在测试里走不到——文案改字全绿。而 json 面两分支恒同形(`data = {directory, files}`),`--json` 断言钉不住人读第二行。落点:闸 4c 的平行表 `EXTRA_EMPTY_HUMAN_CASES`(每 case 自带 `makeRoot` + 人读逐行基线 + json 形状;不改 `EMPTY_HUMAN_BASELINE: Record<SubName, string[]>` 的形状,一个子命令只装一种形状的约束留在原表)。
|
|
@@ -67,14 +67,14 @@ H5(#44)起:
|
|
|
67
67
|
- **json 面现状冻结**:11 个顶层形状逐字不动(双仓核实 `knowledge --json` 零程序化消费者,studio 走 library 直调;统一包壳是纯 churn,删除 `--json` 死面按 ADR-0022 口径另票评估)。三处有意偏离,均不改字段集合:① `snapshot`/`index` 的 `--json` 由单行紧凑改为两空格缩进(分派单点化的直接结果,与其余 9 个子操作同形);② `sync-rag` 的两个空态此前把人类文案直接打到 `--json` 的 stdout(吐出的不是 JSON),现由 display model 承担,json 面恒为 `{directory, files}`;③ `stats` 人读面 `verified` 成熟度的颜色随 `toneForMaturity` 单表变为青色(此前 list 青 / stats 黄两处手抄不一致,文字不变)。
|
|
68
68
|
- 与 #134(KnowledgeAudit 收 store)独立落地,唯一交点 `knowledgeAudit` 命令函数——#134 是后到方并已适配:`knowledgeAuditView` 经 `openKnowledgeStore` 取 store 作引擎第一形参、`shortContentThreshold` 作第二形参(构造期不再自建 store)。审计后重建索引「只在人读路径发生」是本票保留的既有策略(`--json` 不写盘),也是 `knowledge.ts` 内被逐行冻结的唯一 `options.json` 分支。
|
|
69
69
|
- init 治理约束段写入落点(studio #302,ADR 2026-08-21 落点模型):新仓 → AGENTS.md `PRESERVE:governance` 段(sync-docs 重新生成时保留);旧模型仓(CLAUDE.md 已有 HARNESS_CONSTRAINTS 标记或 `## Governance Rules` 块)→ 续写 CLAUDE.md,幂等重跑不制造双份正本。治理段「在场守护」由 core 侧 governance_presence checker 承担(段缺失/为空时 check 报警)
|
|
70
|
-
- PRESERVE
|
|
71
|
-
-
|
|
70
|
+
- ADR-0029(文本注入层关停):init 的约束注入段 writer(setupAgentsMdConstraints / setupClaudeMdConstraints / setupGovernanceConstraints)、check 的注入漂移警告块、report 的漂移小节与 prompt 注入清单、retire 的注入段同步全部移除;sync-docs 不再生成 HARNESS_CONSTRAINTS 注入段。PRESERVE 标记保留机制本身保留(通用机制,非约束注入);历史 HARNESS_CONSTRAINTS 段在 `hasGovernanceContract` 中仅作治理契约在场的历史兼容判据
|
|
71
|
+
- marker-range 替换与治理正本探测收口在 `core/constraints/injection-writer`(ADR-0011):init 的 Output Style 段 writer 为「渲染 body + 调 writer」,半标记(单边/乱序)一律拒写告警;注入段落点路由(resolveInjectionTarget / resolveGovernanceLanding)已随 ADR-0029 删除
|
|
72
72
|
- knowledge 命令包含 11 个子操作(list/search/import/decay/stats/sync-rag/audit/snapshot/migrate/index/health);upsert/sync-status 已迁至 studio CLI(harness#110,二者硬编码 localhost Studio 内部端点,不属 harness「通用框架、文件驱动」定位)
|
|
73
73
|
- `stats` / `health` 的飞轮数字不在 CLI 内计算:分子口径唯一实现是 `knowledge/flywheel-metrics.ts`(`evaluateFlywheel`,ADR-0013),本层只做百分比取整/一位小数与字段名映射;`.consumption-stats.json` 的读取留在各命令(module 零 IO)
|
|
74
74
|
- 特殊路由(选项条件、子命令兜底、裸跑语义)表达在定义表的 optionRoutes / subcommands / subcommandStrict / bareRunsAction 字段,bin 是纯通用引擎、不含单命令知识
|
|
75
75
|
- **数值旗帜装配口径(harness#152)**:`<n>` / `<rate>` 这类数值旗帜经 commander 到命令侧**恒是字符串**(`defaultValue: '50'` 也是),所以入参声明必须跟着运行时形状走——装配点之前声明 `string`(或双形 `string | number`,如 `failure --limit`),数值只在装配点转一次。装配点有两处:**有 `mapActionArgs` 的命令在定义表装配**(`status --hours`、`constraints report` 三阈值、门禁 `--coverage-threshold`/`--bundle-threshold`/`--min-reviewers`),**knowledge 族是「纯投影条目」故编组归命令模块**(`knowledgeSearchCommand` 的 `--limit`、`knowledgeAudit` 的 `--threshold`);两形都对,判据是「声明与运行时一致」,因此 `as any` 不该出现在这条链上(它替编译器记住了旗帜的实际形状,去掉 `parseInt` 就是往数值槽塞字符串)。**转不出来必须 fail-loud**(`logError` + `usage-error`,非零退出):下游兜底写的多是 `?? 缺省值`,而 `??` 只兜 `null`/`undefined`、NaN 照单全收,于是一句 `len < NaN` 恒 false 就把一道质量判定静默关掉、退出码仍是 0(#152 病灶正本:`knowledge audit --threshold abc`)。上游按口径挡住的,引擎侧不再重复兜(库面向的守卫已落 harness#163:`KnowledgeAudit` 构造器对 `shortContentThreshold`/`staleDays`/`promotionBlockDays` 三槽的显式脏值(NaN/±Infinity/负数)构造期抛 `TypeError`,未传/显式 `undefined` 走缺省、显式 `0` 合法)。可检面:`__tests__/knowledge-audit-threshold.test.ts`(脏值 fail-loud / 缺省 '50' 与未传同果 / 显式 '0' 不被当未传 / `knowledge.ts` 零 `as any`)+ `__tests__/bin-exit-mapping.test.ts`(对外退出码 1 的端到端证据);knowledge 的 `kind:` 逐行冻结豁免表在 `__tests__/knowledge-view.test.ts` 闸 1(#152 起两行、#154 起三行,理由随表注)。其余数值旗帜的脏输入后果(fail-closed 与静默按缺省两类)逐项判定见 #152 票面,#152 只收 `knowledge audit --threshold` 一处;**harness#154 已把口径推广到全部数值旗帜**(已推广清单如下),**唯一解析器 = `src/utils/numeric-flag.ts`**(本条目指名的正本:`parseNumericFlag` 判别 + `numericFlagMessage` 文案模板 + `requireNumericFlag` 抛出 + `NumericFlagError`;`parseInt`/`parseFloat` 的前缀截断 `'30abc'→30`、`'1e2'→1` 同属静默误读,禁回)。两消费形同一条判定规则:定义表 `mapActionArgs` 站点(`constraints report` 三阈值、`performance --coverage-threshold`/`--bundle-threshold`、`review --min-reviewers`)走 `requireNumericFlag` 抛出、bin 在 `runDefinition` 捕获映射 usage-error——定义表是纯数据模块返回不了 CommandResult,装配失败的通道只此一条,bin 不因此新增单命令知识;命令模块站点(`knowledge search --limit`、`failure list --limit`、`passes-gate --coverage-threshold` 的 `coverageCheck`)走 `parseNumericFlag` 就地 `logError` + `usage-error`,且都先于 store/recorder 构造与任何执行(脏值零落盘)。#152 的 `assembleShortContentThreshold` 改为委派 `parseNumericFlag`(函数面与 #152 报错文案保留、冻结测试钉死,未新建第二套解析器)。**#152 判为 fail-closed 的两处不保留、一并收敛 fail-loud**(#154 裁定):`passes-gate --coverage-threshold` 的「覆盖率不足 (x% < NaN%)」自证异常文案与 `review --min-reviewers` 的恒不通过——脏输入属用法错误、自证异常的文案即缺陷;合法输入路径逐字不变,退出码面 review 1→1、passes-gate coverage 脏值 0→1 属缺陷修正(定级与 CHANGELOG 入账见该票)。边界不动:门禁/引擎构造器侧的 falsy/`??` 约定是库面向语义(显式 `0` 在 performance 门禁内仍 = 不检查该项),与 #163 同例不重复兜;`status --hours` 是死旗帜,另族另票。可检面新增:`src/utils/__tests__/numeric-flag.test.ts`(解析规则矩阵)+ `src/cli/commands/__tests__/numeric-flag-assembly.test.ts`(定义表逐旗帜装配 + 命令入口 + 端到端 spawn:脏输入 / 合法路径对拍 / 显式 `0`)
|
|
76
76
|
- **命令 interface = `CommandResult` + 注入 io(架构评审候选7)**:命令实现一律声明 `Promise<CommandResult>`(判别联合 `ok|skip|fail|usage-error`,`fail`/`usage-error` 必附可定位的 `reason`,多闸门命令 reason 含 `gate <id>`),末位可选形参 `io: CommandIO = processIO`;类型与写入面在 `src/cli/command-contract.ts`
|
|
77
|
-
- **writer 层 `io` 必传,缺省只留在对外命令入口(harness#149)**:`= processIO` 的缺省是给 bin 不注入时的对外入口用的(每个命令模块一处,如 `init()`)。命令内部的落盘 writer 与它们的路由一律收**必传** `io`——缺省留在链路上时,调用点漏传不报错,只是静默把提示兜到真实 stdout:`captureIO()` 的测试面因此天然缺那批行,`⚠️ …标记残缺…请人工修复` 这类唯一提示写坏了没有测试会红。init
|
|
77
|
+
- **writer 层 `io` 必传,缺省只留在对外命令入口(harness#149)**:`= processIO` 的缺省是给 bin 不注入时的对外入口用的(每个命令模块一处,如 `init()`)。命令内部的落盘 writer 与它们的路由一律收**必传** `io`——缺省留在链路上时,调用点漏传不报错,只是静默把提示兜到真实 stdout:`captureIO()` 的测试面因此天然缺那批行,`⚠️ …标记残缺…请人工修复` 这类唯一提示写坏了没有测试会红。init 治理段的 writer 已按此收口(ADR-0029 后只剩 Output Style 段一个 writer);机器可检:`__tests__/init-governance-io.test.ts`(源形状闸:`init.ts` 内该缺省只剩 `init()` 一处 + writer 签名必传 + `setupGovernance` 调用点逐个传 io)与行为面(临时目录真跑 init,断言 Output Style 段提示与其 `⚠️ …标记残缺…请人工修复` 告警落进注入面)
|
|
78
78
|
- **本目录零 `process.exit` / `process.exitCode`**:kind → 退出码的唯一映射在 `bin/harness.js`(`ok`/`skip` → 0,`fail`/`usage-error` → 1,未知 kind fail-closed)。历史上两处条件式(`command --level` 按严重级、门禁按 passed)已由命令侧译成 kind;`sync-docs --check` 的漂移改由实现返回 `fail`(定义表的 `afterRun` 逃生门随之废除)
|
|
79
79
|
- **输出不得直接用 console**:流式打印走 `log(io, …)` / `logError(io, …)`(`util.format` + 换行,与 console 逐字节等价,见 `src/cli/__tests__/command-contract.test.ts`);测试断言输出用 `captureIO()`,不再 `spyOn(process, 'exit')`;`--json` 输出断言用 `lastJsonOutput(io)`(JSON.parse 正本,harness#108)。例外:`constraints retire` 交互正文仍走 console(其 `RetireIO` 只注入 readline 流),退役结果打印已接注入流
|
|
80
80
|
- **#95 同型扫荡结论**:两门禁站点已修(`passes-gate` 执行/证据落 projectPath、`acceptance` 去掉 `tasksPath: './tasks.yml'` 相对默认值)。逐点判定后的豁免四处,理由与站点原文一起冻结在 `__tests__/project-path-convention.test.ts` 的豁免表:`release` 的 `pkgPath`(定义表本无 `-p`,pkgPath 就是该命令唯一的根且已逐个传给每条 `run(cmd, pkgPath)`,不构成半失效;给发布流水线新增 `-p` 属新能力)、`command` 的 `evaluate({ projectPath: process.cwd(), … })`(`CommandGate` 只做命令串正则判定、零文件读写,且该命令定义表里没有 `-p`,取 cwd 仅为满足 `GateContext` 形状,架构评审候选1)、`constraints` 的 `join(process.cwd(), 'package.json')`(报的是 harness 自身包版本,主锚 `__dirname`,cwd 仅兜底)、`core/spec/validator.ts` 的 `schemaPath: './specs/schemas'`(**确是同型病灶**:`validateAll(projectPath)` 用 projectPath 找 spec 文件、却用 cwd 找 schema;但 `validateFile`/`loadSchema` 签名里没有根,补齐要穿透整个 spec 域,留待 spec 单票收口)
|
|
@@ -68,7 +68,7 @@ function write(dir: string, rel: string, content: string): void {
|
|
|
68
68
|
|
|
69
69
|
const TABLE = ['| 能力 | 路径 | 说明 |', '|---|---|---|'];
|
|
70
70
|
|
|
71
|
-
/** 一个「两 checker 都会真评估」的项目:源码根下两个已登记文件 +
|
|
71
|
+
/** 一个「两 checker 都会真评估」的项目:源码根下两个已登记文件 + 一条 trace */
|
|
72
72
|
function fixtureRepo(): string {
|
|
73
73
|
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'harness-read-count-'));
|
|
74
74
|
git(dir, 'init', '-q');
|
|
@@ -76,11 +76,6 @@ function fixtureRepo(): string {
|
|
|
76
76
|
git(dir, 'config', 'user.name', 'Fixture');
|
|
77
77
|
write(dir, 'README.md', '# fixture\n');
|
|
78
78
|
write(dir, '.harness/config.yml', 'preset: standard\n');
|
|
79
|
-
write(
|
|
80
|
-
dir,
|
|
81
|
-
'.harness/custom-constraints.yml',
|
|
82
|
-
'custom_constraints:\n ab_custom_rule:\n level: guideline\n rule: 自定义规则\n message: 自定义提示\n trigger: manual\n'
|
|
83
|
-
);
|
|
84
79
|
write(
|
|
85
80
|
dir,
|
|
86
81
|
'CAPABILITIES.md',
|
|
@@ -131,25 +126,21 @@ describe('一次 check 的文件读取计数闸(ADR-0023 决策 5 ①)', ()
|
|
|
131
126
|
const result = await check({ staged: true, projectPath: dir }, io);
|
|
132
127
|
expect(result.kind).toBe('ok');
|
|
133
128
|
// 正对照:两个吃能力表的 checker 确实跑了(否则下面的断言是空跑)
|
|
134
|
-
expect(io.outText()).toContain('自定义约束: 1 条');
|
|
135
129
|
expect(io.outText()).toContain('触发条件: module_modification');
|
|
136
130
|
|
|
137
131
|
expect([...readCounts().entries()].sort()).toEqual(
|
|
138
132
|
[
|
|
139
133
|
// —— 一次运行一份:本 ADR 的承诺面 ——
|
|
140
134
|
['.harness/config.yml', 1],
|
|
141
|
-
['.harness/custom-constraints.yml', 1],
|
|
142
135
|
['CAPABILITIES.md', 1],
|
|
143
136
|
['.harness/logs/traces.log', 2],
|
|
144
137
|
['.harness/.state.json', 1], // ADR-0026:智能提示经 StateIO 读状态,恰一次
|
|
138
|
+
['AGENTS.md', 1],
|
|
145
139
|
// —— 已知例外(改动需明写理由)——
|
|
146
140
|
// traces.log = 2:head 50(智能提示的阈值判定,决策 3)与 tail 20(有无失败证据,
|
|
147
141
|
// 决策 4)是两个不同窗口的**有界**读;并成一个窗口就等于回到整读。
|
|
148
|
-
// AGENTS.md =
|
|
149
|
-
//
|
|
150
|
-
// 各尝试读一次(读失败的尝试同样计入)。
|
|
151
|
-
['AGENTS.md', 3],
|
|
152
|
-
['CLAUDE.md', 2],
|
|
142
|
+
// AGENTS.md = 1:ADR-0029 注入漂移检测与 custom 约束面关停后,治理文档只剩
|
|
143
|
+
// governance_presence 一处读者(夹具 CLAUDE.md 不存在且不再被探测,归零)。
|
|
153
144
|
].sort()
|
|
154
145
|
);
|
|
155
146
|
});
|
|
@@ -159,8 +150,6 @@ describe('一次 check 的文件读取计数闸(ADR-0023 决策 5 ①)', ()
|
|
|
159
150
|
|
|
160
151
|
const except = new Set([
|
|
161
152
|
path.join('.harness', 'logs', 'traces.log'), // 两个窗口的有界读,见上表理由
|
|
162
|
-
'AGENTS.md', // 治理文档,ADR-0023 决策表明示不处理
|
|
163
|
-
'CLAUDE.md',
|
|
164
153
|
]);
|
|
165
154
|
const offenders = [...readCounts()].filter(
|
|
166
155
|
([file, n]) => !except.has(file) && n > 1
|
|
@@ -62,13 +62,13 @@ jest.mock('chalk', () => ({
|
|
|
62
62
|
red: jest.fn((str: string) => str),
|
|
63
63
|
}));
|
|
64
64
|
|
|
65
|
-
function fakeConstraint(id: string,
|
|
65
|
+
function fakeConstraint(id: string, severity: 'error' | 'warning') {
|
|
66
66
|
return {
|
|
67
67
|
kind: 'check' as const,
|
|
68
68
|
id,
|
|
69
69
|
rule: 'test',
|
|
70
70
|
message: `msg-${id}`,
|
|
71
|
-
|
|
71
|
+
severity,
|
|
72
72
|
trigger: 'code_implementation',
|
|
73
73
|
enforcement: 'test',
|
|
74
74
|
};
|
|
@@ -83,21 +83,21 @@ describe('check 命令 skip 输出', () => {
|
|
|
83
83
|
jest.clearAllMocks();
|
|
84
84
|
});
|
|
85
85
|
|
|
86
|
-
it('skipped
|
|
86
|
+
it('skipped 单独列示,不计入 error 级通过条数', async () => {
|
|
87
87
|
mockChecker.checkConstraints.mockResolvedValue({
|
|
88
88
|
passed: true,
|
|
89
|
-
|
|
90
|
-
{ id: 'law_pass',
|
|
91
|
-
{ id: 'law_skip',
|
|
89
|
+
errors: [
|
|
90
|
+
{ id: 'law_pass', severity: 'error', satisfied: true, checkedAt: new Date(), constraint: fakeConstraint('law_pass', 'error') },
|
|
91
|
+
{ id: 'law_skip', severity: 'error', satisfied: true, skipped: true, checkedAt: new Date(), constraint: fakeConstraint('law_skip', 'error') },
|
|
92
92
|
],
|
|
93
|
-
|
|
93
|
+
warnings: [],
|
|
94
94
|
warningCount: 0,
|
|
95
95
|
});
|
|
96
96
|
|
|
97
97
|
await check({ preset: 'default', staged: false }, io);
|
|
98
98
|
|
|
99
99
|
// 通过条数只计实际评估的 1 条
|
|
100
|
-
expect(io.outText()).toContain('
|
|
100
|
+
expect(io.outText()).toContain('error 级约束: 全部通过 (1 条)');
|
|
101
101
|
// skipped 单独列示
|
|
102
102
|
expect(io.outText()).toContain('跳过评估: 1 条');
|
|
103
103
|
expect(io.outText()).toContain('- law_skip');
|
|
@@ -105,20 +105,20 @@ describe('check 命令 skip 输出', () => {
|
|
|
105
105
|
expect(io.outText()).toContain('约束检查通过');
|
|
106
106
|
});
|
|
107
107
|
|
|
108
|
-
it('
|
|
108
|
+
it('warning 级通过计数排除 skipped', async () => {
|
|
109
109
|
mockChecker.checkConstraints.mockResolvedValue({
|
|
110
110
|
passed: true,
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
{ id: 'g_pass',
|
|
114
|
-
{ id: 'g_skip',
|
|
111
|
+
errors: [],
|
|
112
|
+
warnings: [
|
|
113
|
+
{ id: 'g_pass', severity: 'warning', satisfied: true, checkedAt: new Date(), constraint: fakeConstraint('g_pass', 'warning') },
|
|
114
|
+
{ id: 'g_skip', severity: 'warning', satisfied: true, skipped: true, checkedAt: new Date(), constraint: fakeConstraint('g_skip', 'warning') },
|
|
115
115
|
],
|
|
116
116
|
warningCount: 0,
|
|
117
117
|
});
|
|
118
118
|
|
|
119
119
|
await check({ preset: 'default', staged: false }, io);
|
|
120
120
|
|
|
121
|
-
expect(io.outText()).toContain('
|
|
121
|
+
expect(io.outText()).toContain('warning 级约束: 1/1 通过');
|
|
122
122
|
expect(io.outText()).toContain('跳过评估: 1 条');
|
|
123
123
|
});
|
|
124
124
|
});
|
|
@@ -155,7 +155,7 @@ describe('check command(真 git fixture)', () => {
|
|
|
155
155
|
});
|
|
156
156
|
|
|
157
157
|
describe('判定与输出形状', () => {
|
|
158
|
-
it('
|
|
158
|
+
it('真证据全部满足时逐层通过', async () => {
|
|
159
159
|
const dir = gitRepo();
|
|
160
160
|
stageChange(dir, 'src/existing.ts', 'export const a = 2;\n');
|
|
161
161
|
passTraces(dir);
|
|
@@ -165,16 +165,13 @@ describe('check command(真 git fixture)', () => {
|
|
|
165
165
|
io
|
|
166
166
|
);
|
|
167
167
|
|
|
168
|
-
|
|
169
|
-
expect(io.outText()).toContain('✅
|
|
170
|
-
expect(io.outText()).toContain('✅ 指导原则: 2/2 通过');
|
|
171
|
-
expect(io.outText()).toContain('⏭️ 跳过评估: 1 条(约定未采用或证据未接线,不计通过/失败)');
|
|
172
|
-
expect(io.outText()).toContain('- incremental_progress');
|
|
168
|
+
expect(io.outText()).toContain('✅ error 级约束: 全部通过 (1 条)');
|
|
169
|
+
expect(io.outText()).toContain('✅ warning 级约束: 1/1 通过');
|
|
173
170
|
expect(io.outText()).toContain('✅ 约束检查通过');
|
|
174
171
|
expect(result).toEqual({ kind: 'ok' });
|
|
175
172
|
});
|
|
176
173
|
|
|
177
|
-
it('无验证证据 →
|
|
174
|
+
it('无验证证据 → 真 error 级拦截,经异常外溢为 fail', async () => {
|
|
178
175
|
const dir = gitRepo();
|
|
179
176
|
stageChange(dir, 'src/existing.ts', 'export const a = 2;\n');
|
|
180
177
|
write(dir, '.harness/logs/traces.log', '{"constraintId":"fixture","result":"fail"}\n');
|
|
@@ -207,7 +204,7 @@ describe('check command(真 git fixture)', () => {
|
|
|
207
204
|
expect(result.kind).toBe('fail');
|
|
208
205
|
});
|
|
209
206
|
|
|
210
|
-
it('staged 硬编码凭证 →
|
|
207
|
+
it('staged 硬编码凭证 → warning 级警告但不阻断', async () => {
|
|
211
208
|
const dir = gitRepo();
|
|
212
209
|
stageChange(
|
|
213
210
|
dir,
|
|
@@ -223,7 +220,7 @@ describe('check command(真 git fixture)', () => {
|
|
|
223
220
|
io
|
|
224
221
|
);
|
|
225
222
|
|
|
226
|
-
expect(io.outText()).toContain('⚠️
|
|
223
|
+
expect(io.outText()).toContain('⚠️ warning 级约束警告: 1 条');
|
|
227
224
|
expect(io.outText()).toContain('- no_hardcoded_credentials');
|
|
228
225
|
expect(io.outText()).toContain('✅ 约束检查通过');
|
|
229
226
|
expect(result).toEqual({ kind: 'ok' });
|
|
@@ -313,8 +310,8 @@ describe('check command(真 git fixture)', () => {
|
|
|
313
310
|
);
|
|
314
311
|
|
|
315
312
|
const out = io.outText();
|
|
316
|
-
expect(out).toContain('✅
|
|
317
|
-
expect(out).not.toContain('
|
|
313
|
+
expect(out).toContain('✅ warning 级约束: 1/1 通过');
|
|
314
|
+
expect(out).not.toContain('warning 级约束警告');
|
|
318
315
|
expect(out).toContain('💡 提示: 1 条(不判违规,供参考)');
|
|
319
316
|
expect(out).toContain('- capability_sync:');
|
|
320
317
|
expect(out).toContain('src/nested/deep.ts');
|
|
@@ -398,7 +395,7 @@ describe('check command(真 git fixture)', () => {
|
|
|
398
395
|
const traces = projectTraces(dir).slice(seeded);
|
|
399
396
|
|
|
400
397
|
expect(traces.map(t => t.constraintId)).toEqual(
|
|
401
|
-
expect.arrayContaining(['
|
|
398
|
+
expect.arrayContaining(['no_completion_without_verification', 'no_hardcoded_credentials'])
|
|
402
399
|
);
|
|
403
400
|
expect(traces.every(t => ['pass', 'fail', 'skip'].includes(t.result))).toBe(true);
|
|
404
401
|
expect(traces.every(t => t.projectPath === dir)).toBe(true);
|
|
@@ -462,7 +459,7 @@ describe('check command(真 git fixture)', () => {
|
|
|
462
459
|
});
|
|
463
460
|
|
|
464
461
|
describe('生效集来自真 .harness/config.yml', () => {
|
|
465
|
-
it('
|
|
462
|
+
it('报告禁用与未知约束 id', async () => {
|
|
466
463
|
const dir = gitRepo();
|
|
467
464
|
stageChange(dir, 'src/existing.ts', 'export const a = 2;\n');
|
|
468
465
|
passTraces(dir);
|
|
@@ -470,12 +467,6 @@ describe('check command(真 git fixture)', () => {
|
|
|
470
467
|
dir,
|
|
471
468
|
'.harness/config.yml',
|
|
472
469
|
[
|
|
473
|
-
'custom_constraints:',
|
|
474
|
-
' fixture_rule:',
|
|
475
|
-
' id: fixture_rule',
|
|
476
|
-
' level: guideline',
|
|
477
|
-
' trigger: manual',
|
|
478
|
-
' message: fixture 自定义约束',
|
|
479
470
|
'constraints:',
|
|
480
471
|
' capability_sync:',
|
|
481
472
|
' enabled: false',
|
|
@@ -490,7 +481,6 @@ describe('check command(真 git fixture)', () => {
|
|
|
490
481
|
io
|
|
491
482
|
);
|
|
492
483
|
|
|
493
|
-
expect(io.outText()).toContain('自定义约束: 1 条');
|
|
494
484
|
expect(io.outText()).toContain('已禁用约束: capability_sync, no_such_rule');
|
|
495
485
|
expect(io.outText()).toContain('配置中存在未知约束 id(已忽略,可清理): no_such_rule');
|
|
496
486
|
});
|
|
@@ -546,24 +536,17 @@ describe('check command(真 git fixture)', () => {
|
|
|
546
536
|
expect(result).toEqual({ kind: 'ok' });
|
|
547
537
|
});
|
|
548
538
|
|
|
549
|
-
it('
|
|
539
|
+
it('应该列出 error 级约束', () => {
|
|
550
540
|
const result = listLaws({}, io);
|
|
551
541
|
|
|
552
|
-
expect(io.outText()).toContain('
|
|
542
|
+
expect(io.outText()).toContain('error 级');
|
|
553
543
|
expect(result).toEqual({ kind: 'ok' });
|
|
554
544
|
});
|
|
555
545
|
|
|
556
|
-
it('
|
|
546
|
+
it('应该列出 warning 级约束', () => {
|
|
557
547
|
const result = listLaws({}, io);
|
|
558
548
|
|
|
559
|
-
expect(io.outText()).toContain('
|
|
560
|
-
expect(result).toEqual({ kind: 'ok' });
|
|
561
|
-
});
|
|
562
|
-
|
|
563
|
-
it('应该列出提示', () => {
|
|
564
|
-
const result = listLaws({}, io);
|
|
565
|
-
|
|
566
|
-
expect(io.outText()).toContain('提示');
|
|
549
|
+
expect(io.outText()).toContain('warning 级');
|
|
567
550
|
expect(result).toEqual({ kind: 'ok' });
|
|
568
551
|
});
|
|
569
552
|
});
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* constraints report --json-output 测试(E1 复盘修正 M3.1)
|
|
3
|
+
*
|
|
4
|
+
* 复现的 bug:父命令 constraints 与子命令 report 同名 --json 相撞
|
|
5
|
+
* (definitions.ts 两处注册),commander 把 flag 消费在父命令上,
|
|
6
|
+
* report 的 JSON 分支(constraints-report.ts)不可达——
|
|
7
|
+
* `constraints report --json` 输出的仍是文本格式。
|
|
8
|
+
*
|
|
9
|
+
* 修法(计划允许二选一):子命令换名 --json-output。
|
|
10
|
+
* 理由:父命令 --json 有活跃消费者(studio-agent 取约束 hash),
|
|
11
|
+
* 删除会静默降级其取数;子命令 --json 因本 bug 从未可达、零消费者,
|
|
12
|
+
* 换名零破坏。
|
|
13
|
+
*
|
|
14
|
+
* 两层证据:
|
|
15
|
+
* 1. 定义表不变式(无 dist 依赖):父子命令不得注册同名 option flag
|
|
16
|
+
* (commander 会把子命令位置的同名 flag 消费在父命令上);
|
|
17
|
+
* 2. 端到端 spawn bin/harness.js(dist 存在时):--json-output 真实到达
|
|
18
|
+
* report 的 JSON 分支,父命令 --json 行为不变。
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import * as fs from 'fs';
|
|
22
|
+
import * as os from 'os';
|
|
23
|
+
import * as path from 'path';
|
|
24
|
+
import { spawnSync } from 'child_process';
|
|
25
|
+
import { COMMAND_DEFINITIONS } from '../definitions';
|
|
26
|
+
import { GATE_DEFINITIONS } from '../../../gates/definitions';
|
|
27
|
+
|
|
28
|
+
/** 从 flags 串提取长选项名:'-p, --project-path <path>' → '--project-path' */
|
|
29
|
+
function longFlags(flags: string): string[] {
|
|
30
|
+
return [...flags.matchAll(/--[a-z][a-z-]*/g)].map(m => m[0]);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
describe('定义表不变式:父子命令不得注册同名 option flag', () => {
|
|
34
|
+
const defs = [
|
|
35
|
+
...COMMAND_DEFINITIONS,
|
|
36
|
+
...GATE_DEFINITIONS.filter(g => g.cli).map(g => g.cli!),
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
it('constraints report 与父命令无同名 flag(M3.1 复现场景)', () => {
|
|
40
|
+
const constraints = defs.find(d => d.command === 'constraints')!;
|
|
41
|
+
const parentFlags = new Set((constraints.options ?? []).flatMap(o => longFlags(o.flags)));
|
|
42
|
+
for (const child of constraints.children ?? []) {
|
|
43
|
+
const childFlags = (child.options ?? []).flatMap(o => longFlags(o.flags));
|
|
44
|
+
const collisions = childFlags.filter(f => parentFlags.has(f));
|
|
45
|
+
expect(collisions).toEqual([]);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('全表泛化:任何带 children 的命令都与子命令 flag 不相交', () => {
|
|
50
|
+
for (const def of defs) {
|
|
51
|
+
if (!def.children || def.children.length === 0) continue;
|
|
52
|
+
const parentFlags = new Set((def.options ?? []).flatMap(o => longFlags(o.flags)));
|
|
53
|
+
for (const child of def.children) {
|
|
54
|
+
const childFlags = (child.options ?? []).flatMap(o => longFlags(o.flags));
|
|
55
|
+
const collisions = childFlags.filter(f => parentFlags.has(f));
|
|
56
|
+
expect(collisions).toEqual([]);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// dist 缺失时显式失败而非静默 skip(同 bin-exit-mapping.test.ts 口径,harness#99)
|
|
63
|
+
const repoRoot = path.join(__dirname, '..', '..', '..', '..');
|
|
64
|
+
const distBin = path.join(repoRoot, 'bin', 'harness.js');
|
|
65
|
+
const hasDist = fs.existsSync(path.join(repoRoot, 'dist', 'cli', 'commands'));
|
|
66
|
+
|
|
67
|
+
if (!hasDist && process.env.CI) {
|
|
68
|
+
throw new Error('dist 未构建:bin 端到端 smoke 不得静默 skip —— CI 必须先 npm run build 再 npm test(harness#99)');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const smoke = hasDist ? describe : describe.skip;
|
|
72
|
+
|
|
73
|
+
smoke('端到端:constraints report --json-output 到达 JSON 分支', () => {
|
|
74
|
+
const run = (argv: string[], cwd: string) => spawnSync(process.execPath, [distBin, ...argv], {
|
|
75
|
+
cwd,
|
|
76
|
+
encoding: 'utf-8',
|
|
77
|
+
timeout: 60000,
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('report --json-output → stdout 是报告 JSON(含 stats),退出码 0', () => {
|
|
81
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'harness-m31-'));
|
|
82
|
+
try {
|
|
83
|
+
const r = run(['constraints', 'report', '--json-output'], dir);
|
|
84
|
+
expect(r.status).toBe(0);
|
|
85
|
+
const report = JSON.parse(r.stdout);
|
|
86
|
+
expect(Array.isArray(report.stats)).toBe(true);
|
|
87
|
+
expect(report.stats.length).toBeGreaterThan(0);
|
|
88
|
+
expect(Array.isArray(report.candidates)).toBe(true);
|
|
89
|
+
} finally {
|
|
90
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('父命令 constraints --json 行为不变(活跃消费者面)', () => {
|
|
95
|
+
const r = run(['constraints', '--json'], repoRoot);
|
|
96
|
+
expect(r.status).toBe(0);
|
|
97
|
+
const meta = JSON.parse(r.stdout);
|
|
98
|
+
expect(typeof meta.hash).toBe('string');
|
|
99
|
+
expect(meta.counts).toBeDefined();
|
|
100
|
+
});
|
|
101
|
+
});
|