@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,44 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* CLAUDE.md 约束注入段渲染(ADR-0001)
|
|
4
|
-
*
|
|
5
|
-
* 纯函数:输入生效约束集 + harness 版本,输出标记区间的期望文本。
|
|
6
|
-
* 与写文件分离——init 注入与 P6 注入漂移校验(期望 vs 实际)共用此渲染。
|
|
7
|
-
*/
|
|
8
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
-
exports.CONSTRAINTS_END_MARKER = exports.CONSTRAINTS_START_MARKER = void 0;
|
|
10
|
-
exports.renderConstraintsSection = renderConstraintsSection;
|
|
11
|
-
/** 约束注入段开始标记 */
|
|
12
|
-
exports.CONSTRAINTS_START_MARKER = '<!-- HARNESS_CONSTRAINTS_START -->';
|
|
13
|
-
/** 约束注入段结束标记 */
|
|
14
|
-
exports.CONSTRAINTS_END_MARKER = '<!-- HARNESS_CONSTRAINTS_END -->';
|
|
15
|
-
/**
|
|
16
|
-
* 渲染 CLAUDE.md 约束注入段(含 START/END 标记,不含 `## Governance Rules` 标题)
|
|
17
|
-
*
|
|
18
|
-
* 结构:### Iron Laws / ### Guidelines / ### Prompts(行为约束),
|
|
19
|
-
* 条目为 `- **id**: promptInjection`;无 promptInjection 的 check 条目
|
|
20
|
-
* (如 docs_freshness)不出现在注入段。返回文本以换行结尾。
|
|
21
|
-
*
|
|
22
|
-
* @param constraints 生效约束集(getEffectiveConstraints 的输出)
|
|
23
|
-
* @param version harness 版本号(写入 `<!-- version: x -->` 供漂移校验)
|
|
24
|
-
*/
|
|
25
|
-
function renderConstraintsSection(constraints, version) {
|
|
26
|
-
const lines = [exports.CONSTRAINTS_START_MARKER, `<!-- version: ${version} -->`];
|
|
27
|
-
const renderGroup = (level, heading, first) => {
|
|
28
|
-
const items = constraints.filter(c => c.level === level && c.promptInjection);
|
|
29
|
-
if (items.length === 0)
|
|
30
|
-
return;
|
|
31
|
-
if (!first)
|
|
32
|
-
lines.push('');
|
|
33
|
-
lines.push(heading);
|
|
34
|
-
for (const c of items) {
|
|
35
|
-
lines.push(`- **${c.id}**: ${c.promptInjection}`);
|
|
36
|
-
}
|
|
37
|
-
};
|
|
38
|
-
renderGroup('iron_law', '### Iron Laws (违反将阻断)', true);
|
|
39
|
-
renderGroup('guideline', '### Guidelines (应遵循)', lines.length === 2);
|
|
40
|
-
renderGroup('prompt', '### Prompts (行为约束)', lines.length === 2);
|
|
41
|
-
lines.push(exports.CONSTRAINTS_END_MARKER);
|
|
42
|
-
return lines.join('\n') + '\n';
|
|
43
|
-
}
|
|
44
|
-
//# sourceMappingURL=injection-renderer.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"injection-renderer.js","sourceRoot":"","sources":["../../../src/core/constraints/injection-renderer.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;;AAmBH,4DAmBC;AAlCD,gBAAgB;AACH,QAAA,wBAAwB,GAAG,oCAAoC,CAAC;AAC7E,gBAAgB;AACH,QAAA,sBAAsB,GAAG,kCAAkC,CAAC;AAEzE;;;;;;;;;GASG;AACH,SAAgB,wBAAwB,CAAC,WAAyB,EAAE,OAAe;IACjF,MAAM,KAAK,GAAa,CAAC,gCAAwB,EAAE,iBAAiB,OAAO,MAAM,CAAC,CAAC;IAEnF,MAAM,WAAW,GAAG,CAAC,KAA0C,EAAE,OAAe,EAAE,KAAc,EAAE,EAAE;QAClG,MAAM,KAAK,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,KAAK,IAAI,CAAC,CAAC,eAAe,CAAC,CAAC;QAC9E,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO;QAC/B,IAAI,CAAC,KAAK;YAAE,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC3B,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACpB,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;YACtB,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,eAAe,EAAE,CAAC,CAAC;QACpD,CAAC;IACH,CAAC,CAAC;IAEF,WAAW,CAAC,UAAU,EAAE,uBAAuB,EAAE,IAAI,CAAC,CAAC;IACvD,WAAW,CAAC,WAAW,EAAE,sBAAsB,EAAE,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC;IACrE,WAAW,CAAC,QAAQ,EAAE,oBAAoB,EAAE,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC;IAEhE,KAAK,CAAC,IAAI,CAAC,8BAAsB,CAAC,CAAC;IACnC,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;AACjC,CAAC"}
|
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* check 命令注入漂移警告测试(ADR-0001 决策 7,P6)
|
|
3
|
-
*
|
|
4
|
-
* 警告不阻断:漂移项目跑 check 仍 0 退出;版本漂移单独显眼警告;
|
|
5
|
-
* 无漂移零输出。使用真实临时目录(漂移检测读真实 fs),
|
|
6
|
-
* 仅 mock checker(直接判过)与 git 调用。
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import * as fs from 'fs';
|
|
10
|
-
import { captureIO, type CapturingIO } from '../../command-contract';
|
|
11
|
-
import * as path from 'path';
|
|
12
|
-
import { check } from '../check';
|
|
13
|
-
import { renderConstraintsSection } from '../../../core/constraints/injection-renderer';
|
|
14
|
-
import { getEffectiveConstraints } from '../../../core/effective-constraints';
|
|
15
|
-
import { createProjectFixture } from '../../../test-setup/project-fixture';
|
|
16
|
-
|
|
17
|
-
// 命令 per-run 构造 checker(harness#88):构造替身即控制 checkConstraints 返回值
|
|
18
|
-
const mockChecker = { checkConstraints: jest.fn() };
|
|
19
|
-
jest.mock('../../../core/constraints/checker', () => ({
|
|
20
|
-
ConstraintChecker: jest.fn(function () {
|
|
21
|
-
return mockChecker;
|
|
22
|
-
}),
|
|
23
|
-
}));
|
|
24
|
-
|
|
25
|
-
// 命令 per-run 锚根构造收集器(harness#139):构造替身即拦截 trace 落盘
|
|
26
|
-
jest.mock('../../../monitoring/traces', () => ({
|
|
27
|
-
TraceCollector: jest.fn(function () {
|
|
28
|
-
return { record: jest.fn() };
|
|
29
|
-
}),
|
|
30
|
-
}));
|
|
31
|
-
|
|
32
|
-
jest.mock('../../../utils/exec', () => ({
|
|
33
|
-
execAsync: jest.fn().mockResolvedValue({ stdout: '', stderr: '' }),
|
|
34
|
-
}));
|
|
35
|
-
|
|
36
|
-
jest.mock('child_process', () => ({
|
|
37
|
-
exec: jest.fn(),
|
|
38
|
-
execSync: jest.fn(() => Buffer.from('')),
|
|
39
|
-
}));
|
|
40
|
-
|
|
41
|
-
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
42
|
-
const REAL_VERSION = require('../../../../package.json').version as string;
|
|
43
|
-
|
|
44
|
-
function writeClaudeMd(root: string, version: string): void {
|
|
45
|
-
const section =
|
|
46
|
-
'## Governance Rules\n' + renderConstraintsSection(getEffectiveConstraints(root), version);
|
|
47
|
-
fs.writeFileSync(path.join(root, 'CLAUDE.md'), `# Test Project\n\n${section}`, 'utf-8');
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
describe('check 命令注入漂移警告', () => {
|
|
51
|
-
let io: CapturingIO;
|
|
52
|
-
|
|
53
|
-
beforeEach(() => {
|
|
54
|
-
|
|
55
|
-
io = captureIO();
|
|
56
|
-
jest.clearAllMocks();
|
|
57
|
-
mockChecker.checkConstraints.mockResolvedValue({
|
|
58
|
-
passed: true,
|
|
59
|
-
ironLaws: [],
|
|
60
|
-
guidelines: [],
|
|
61
|
-
warningCount: 0,
|
|
62
|
-
});
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
const outputText = () => io.outText();
|
|
66
|
-
|
|
67
|
-
it('版本漂移:黄色警告块 + ⚠️⚠️ 版本行,但不阻断(exit 未调用,仍判通过)', async () => {
|
|
68
|
-
const root = createProjectFixture({ name: 'harness-check-drift-test' });
|
|
69
|
-
writeClaudeMd(root, '0.0.1-old');
|
|
70
|
-
|
|
71
|
-
const result = await check({ preset: 'standard', staged: false, projectPath: root }, io);
|
|
72
|
-
|
|
73
|
-
const output = outputText();
|
|
74
|
-
expect(output).toContain('约束注入漂移');
|
|
75
|
-
expect(output).toContain('⚠️⚠️');
|
|
76
|
-
expect(output).toContain('agent 上下文中的规则与已安装 harness 版本不一致');
|
|
77
|
-
expect(output).toContain('0.0.1-old');
|
|
78
|
-
expect(output).toContain('npx @dommaker/harness init');
|
|
79
|
-
// 不阻断:检查仍通过、未调用 process.exit
|
|
80
|
-
expect(output).toContain('约束检查通过');
|
|
81
|
-
expect(result.kind).toBe('ok');
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
it('内容漂移:手改一条 → 警告块含缺失/多余计数,exit 未调用', async () => {
|
|
85
|
-
const root = createProjectFixture({ name: 'harness-check-drift-test' });
|
|
86
|
-
writeClaudeMd(root, REAL_VERSION);
|
|
87
|
-
const claudeMdPath = path.join(root, 'CLAUDE.md');
|
|
88
|
-
const content = fs.readFileSync(claudeMdPath, 'utf-8');
|
|
89
|
-
const originalLine = content.split('\n').find(l => l.startsWith('- **'))!;
|
|
90
|
-
fs.writeFileSync(claudeMdPath, content.replace(originalLine, originalLine.replace(/: .+$/, ': 篡改')), 'utf-8');
|
|
91
|
-
|
|
92
|
-
const result = await check({ preset: 'standard', staged: false, projectPath: root }, io);
|
|
93
|
-
|
|
94
|
-
const output = outputText();
|
|
95
|
-
expect(output).toContain('内容漂移: 缺失 1 条 / 多余 1 条');
|
|
96
|
-
expect(output).toContain('约束检查通过');
|
|
97
|
-
expect(result.kind).toBe('ok');
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
it('无漂移:零警告输出(不增加噪音)', async () => {
|
|
101
|
-
const root = createProjectFixture({ name: 'harness-check-drift-test' });
|
|
102
|
-
writeClaudeMd(root, REAL_VERSION);
|
|
103
|
-
|
|
104
|
-
const result = await check({ preset: 'standard', staged: false, projectPath: root }, io);
|
|
105
|
-
|
|
106
|
-
const output = outputText();
|
|
107
|
-
expect(output).not.toContain('注入漂移');
|
|
108
|
-
expect(output).toContain('约束检查通过');
|
|
109
|
-
expect(result.kind).toBe('ok');
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
it('未注入(无 CLAUDE.md):不警告', async () => {
|
|
113
|
-
const root = createProjectFixture({ name: 'harness-check-drift-test' });
|
|
114
|
-
|
|
115
|
-
const result = await check({ preset: 'standard', staged: false, projectPath: root }, io);
|
|
116
|
-
|
|
117
|
-
expect(outputText()).not.toContain('注入漂移');
|
|
118
|
-
expect(result.kind).toBe('ok');
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
// ── 生效集来源与漂移比对的同一性(ADR-0023 步骤 4.5)──────────────────
|
|
122
|
-
// 改前 CLI 给 -p 塞了缺省值 'standard',「没传」与「传了」不可区分,
|
|
123
|
-
// 于是「--preset 仅在无自定义配置时覆盖」这条规则恒被触发:项目 config.yml 的
|
|
124
|
-
// preset 静默失效,而漂移侧按 config.yml 算 → 两边稳定对不上(该报的漂移不报)。
|
|
125
|
-
|
|
126
|
-
it('config.yml 的 preset 不再被 CLI 缺省顶掉:不带 -p 按 relaxed 执法且零漂移', async () => {
|
|
127
|
-
const root = createProjectFixture({
|
|
128
|
-
name: 'harness-check-drift-relaxed',
|
|
129
|
-
config: 'preset: relaxed\n',
|
|
130
|
-
});
|
|
131
|
-
writeClaudeMd(root, REAL_VERSION); // getEffectiveConstraints(root) 走 config.yml = relaxed
|
|
132
|
-
|
|
133
|
-
const result = await check({ staged: false, projectPath: root }, io);
|
|
134
|
-
|
|
135
|
-
// relaxed 裁掉的内置条目会出现在「已禁用约束」行——改前 CLI 塞了 standard,这行根本不出现
|
|
136
|
-
const output = outputText();
|
|
137
|
-
expect(output).toContain('已禁用约束:');
|
|
138
|
-
expect(output).not.toContain('内容漂移');
|
|
139
|
-
expect(output).toContain('约束检查通过');
|
|
140
|
-
expect(result.kind).toBe('ok');
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
it('显式 -p 时,漂移比对用的就是本 run 实际执法的那份', async () => {
|
|
144
|
-
const root = createProjectFixture({
|
|
145
|
-
name: 'harness-check-drift-override',
|
|
146
|
-
config: 'preset: relaxed\n',
|
|
147
|
-
});
|
|
148
|
-
writeClaudeMd(root, REAL_VERSION); // 段里是 relaxed 的少数条目
|
|
149
|
-
|
|
150
|
-
await check({ preset: 'standard', staged: false, projectPath: root }, io);
|
|
151
|
-
|
|
152
|
-
// standard 不裁条目(无「已禁用约束」行),且期望段 = 本 run 执法的 standard
|
|
153
|
-
// → 段里条目只少不多,报「缺失 N 条 / 多余 0 条」
|
|
154
|
-
const output = outputText();
|
|
155
|
-
expect(output).not.toContain('已禁用约束:');
|
|
156
|
-
expect(output).toMatch(/内容漂移: 缺失 [1-9]\d* 条 \/ 多余 0 条/);
|
|
157
|
-
});
|
|
158
|
-
});
|
|
@@ -1,466 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* init 注入重写测试(ADR-0001 P3)
|
|
3
|
-
*
|
|
4
|
-
* - renderConstraintsSection 纯函数
|
|
5
|
-
* - setupClaudeMdConstraints:消费生效集、幂等、尊重 config.yml 禁用与 scenes
|
|
6
|
-
* - setupClaudeMdOutputStyle:标记化三种情形(无段插入 / 旧 harness 段迁移 / 用户自有段跳过)
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { describe, it, expect, beforeEach, afterEach, jest } from '@jest/globals';
|
|
10
|
-
import { captureIO, type CapturingIO } from '../../command-contract';
|
|
11
|
-
import * as fs from 'fs';
|
|
12
|
-
import * as path from 'path';
|
|
13
|
-
import {
|
|
14
|
-
renderConstraintsSection,
|
|
15
|
-
CONSTRAINTS_START_MARKER,
|
|
16
|
-
CONSTRAINTS_END_MARKER,
|
|
17
|
-
} from '../../../core/constraints/injection-renderer';
|
|
18
|
-
import {
|
|
19
|
-
setupClaudeMdConstraints,
|
|
20
|
-
setupClaudeMdOutputStyle,
|
|
21
|
-
setupAgentsMdConstraints,
|
|
22
|
-
setupGovernanceConstraints,
|
|
23
|
-
} from '../init';
|
|
24
|
-
import { getEffectiveConstraints } from '../../../core/effective-constraints';
|
|
25
|
-
import { getAllConstraints } from '../../../core/constraints/definitions';
|
|
26
|
-
import type { Constraint } from '../../../types/constraint';
|
|
27
|
-
|
|
28
|
-
const OUTPUT_STYLE_START = '<!-- HARNESS_OUTPUT_STYLE_START -->';
|
|
29
|
-
const OUTPUT_STYLE_END = '<!-- HARNESS_OUTPUT_STYLE_END -->';
|
|
30
|
-
|
|
31
|
-
const makeConstraint = (overrides: Partial<Constraint>): Constraint => ({
|
|
32
|
-
id: 'x',
|
|
33
|
-
kind: 'prompt',
|
|
34
|
-
level: 'prompt',
|
|
35
|
-
rule: 'R',
|
|
36
|
-
message: 'M',
|
|
37
|
-
trigger: 'manual',
|
|
38
|
-
enforcement: 'none',
|
|
39
|
-
...overrides,
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
let io: CapturingIO;
|
|
43
|
-
beforeEach(() => {
|
|
44
|
-
io = captureIO();
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
describe('renderConstraintsSection(纯函数)', () => {
|
|
48
|
-
it('按 level 分组渲染,含标记与版本行', () => {
|
|
49
|
-
const constraints = [
|
|
50
|
-
makeConstraint({ id: 'iron_a', kind: 'check', level: 'iron_law', promptInjection: 'iron 注入' }),
|
|
51
|
-
makeConstraint({ id: 'guide_a', kind: 'check', level: 'guideline', promptInjection: 'guide 注入' }),
|
|
52
|
-
makeConstraint({ id: 'prompt_a', level: 'prompt', promptInjection: 'prompt 注入' }),
|
|
53
|
-
];
|
|
54
|
-
|
|
55
|
-
const section = renderConstraintsSection(constraints, '9.9.9');
|
|
56
|
-
|
|
57
|
-
expect(section.startsWith(`${CONSTRAINTS_START_MARKER}\n<!-- version: 9.9.9 -->\n`)).toBe(true);
|
|
58
|
-
expect(section.endsWith(`${CONSTRAINTS_END_MARKER}\n`)).toBe(true);
|
|
59
|
-
expect(section).toContain('### Iron Laws');
|
|
60
|
-
expect(section).toContain('### Guidelines');
|
|
61
|
-
expect(section).toContain('### Prompts');
|
|
62
|
-
expect(section).toContain('- **iron_a**: iron 注入');
|
|
63
|
-
expect(section).toContain('- **guide_a**: guide 注入');
|
|
64
|
-
expect(section).toContain('- **prompt_a**: prompt 注入');
|
|
65
|
-
// 分组顺序:Iron Laws → Guidelines → Prompts
|
|
66
|
-
expect(section.indexOf('### Iron Laws')).toBeLessThan(section.indexOf('### Guidelines'));
|
|
67
|
-
expect(section.indexOf('### Guidelines')).toBeLessThan(section.indexOf('### Prompts'));
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
it('无 promptInjection 的 check 条目(如 docs_freshness)不出现在注入段', () => {
|
|
71
|
-
const constraints = [
|
|
72
|
-
makeConstraint({ id: 'docs_freshness', kind: 'check', level: 'iron_law' }),
|
|
73
|
-
makeConstraint({ id: 'iron_a', kind: 'check', level: 'iron_law', promptInjection: 'iron 注入' }),
|
|
74
|
-
];
|
|
75
|
-
|
|
76
|
-
const section = renderConstraintsSection(constraints, '1.0.0');
|
|
77
|
-
|
|
78
|
-
expect(section).not.toContain('docs_freshness');
|
|
79
|
-
expect(section).toContain('- **iron_a**: iron 注入');
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
it('内置全集渲染:docs_freshness 不出现,场景 prompt 由调用方过滤后传入', () => {
|
|
83
|
-
const section = renderConstraintsSection(getAllConstraints(), '1.0.0');
|
|
84
|
-
expect(section).not.toContain('docs_freshness');
|
|
85
|
-
expect(section).toContain('- **no_fuzzy_completion_claim**');
|
|
86
|
-
});
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
describe('setupClaudeMdConstraints(消费生效集)', () => {
|
|
90
|
-
let tempDir: string;
|
|
91
|
-
let claudeMdPath: string;
|
|
92
|
-
|
|
93
|
-
const writeConfig = (yaml: string) => {
|
|
94
|
-
fs.mkdirSync(path.join(tempDir, '.harness'), { recursive: true });
|
|
95
|
-
fs.writeFileSync(path.join(tempDir, '.harness', 'config.yml'), yaml);
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
beforeEach(() => {
|
|
99
|
-
tempDir = fs.mkdtempSync(path.join(process.cwd(), 'temp-test-init-'));
|
|
100
|
-
claudeMdPath = path.join(tempDir, 'CLAUDE.md');
|
|
101
|
-
jest.spyOn(console, 'log').mockImplementation(() => {});
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
afterEach(() => {
|
|
105
|
-
jest.restoreAllMocks();
|
|
106
|
-
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
it('CLAUDE.md 不存在时创建并写入约束段', async () => {
|
|
110
|
-
await setupClaudeMdConstraints(tempDir, io);
|
|
111
|
-
|
|
112
|
-
const content = fs.readFileSync(claudeMdPath, 'utf-8');
|
|
113
|
-
expect(content).toContain('## Governance Rules');
|
|
114
|
-
expect(content).toContain(CONSTRAINTS_START_MARKER);
|
|
115
|
-
expect(content).toContain(CONSTRAINTS_END_MARKER);
|
|
116
|
-
expect(content).toContain('### Iron Laws');
|
|
117
|
-
expect(content).toContain('### Prompts (行为约束)');
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
it('连跑两次输出一致(幂等)', async () => {
|
|
121
|
-
await setupClaudeMdConstraints(tempDir, io);
|
|
122
|
-
const first = fs.readFileSync(claudeMdPath, 'utf-8');
|
|
123
|
-
await setupClaudeMdConstraints(tempDir, io);
|
|
124
|
-
const second = fs.readFileSync(claudeMdPath, 'utf-8');
|
|
125
|
-
expect(second).toBe(first);
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
it('标记区间外内容保持不动', async () => {
|
|
129
|
-
fs.writeFileSync(claudeMdPath, '# My Project\n\n用户自定义内容\n');
|
|
130
|
-
await setupClaudeMdConstraints(tempDir, io);
|
|
131
|
-
const content = fs.readFileSync(claudeMdPath, 'utf-8');
|
|
132
|
-
expect(content).toContain('# My Project');
|
|
133
|
-
expect(content).toContain('用户自定义内容');
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
it('标记残缺(只有单边):拒写不追加并告警(writer 半标记守护,候选1)', async () => {
|
|
137
|
-
const broken = `# My Project\n\n${CONSTRAINTS_START_MARKER}\n半残段\n`;
|
|
138
|
-
fs.writeFileSync(claudeMdPath, broken);
|
|
139
|
-
await setupClaudeMdConstraints(tempDir, io);
|
|
140
|
-
expect(fs.readFileSync(claudeMdPath, 'utf-8')).toBe(broken);
|
|
141
|
-
expect(io.outText()).toContain('残缺');
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
it('尊重 config.yml 禁用:注入段不含被禁条目', async () => {
|
|
145
|
-
writeConfig('constraints:\n no_fuzzy_completion_claim:\n enabled: false\n');
|
|
146
|
-
await setupClaudeMdConstraints(tempDir, io);
|
|
147
|
-
|
|
148
|
-
const content = fs.readFileSync(claudeMdPath, 'utf-8');
|
|
149
|
-
expect(content).not.toContain('no_fuzzy_completion_claim');
|
|
150
|
-
expect(content).toContain('- **no_fix_without_root_cause**');
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
it('scenes 缺省不注入场景专属 prompt;配置 scenes 后注入', async () => {
|
|
154
|
-
writeConfig('preset: standard\n');
|
|
155
|
-
await setupClaudeMdConstraints(tempDir, io);
|
|
156
|
-
let content = fs.readFileSync(claudeMdPath, 'utf-8');
|
|
157
|
-
expect(content).not.toContain('no_skill_without_test');
|
|
158
|
-
|
|
159
|
-
writeConfig('preset: standard\nscenes:\n - agent-skill\n');
|
|
160
|
-
await setupClaudeMdConstraints(tempDir, io);
|
|
161
|
-
content = fs.readFileSync(claudeMdPath, 'utf-8');
|
|
162
|
-
expect(content).toContain('- **no_skill_without_test**');
|
|
163
|
-
expect(content).not.toContain('no_model_for_deterministic');
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
it('注入段文本与 renderConstraintsSection(getEffectiveConstraints()) 一致', async () => {
|
|
167
|
-
writeConfig('constraints:\n no_bypass_checkpoint:\n enabled: false\n');
|
|
168
|
-
await setupClaudeMdConstraints(tempDir, io);
|
|
169
|
-
|
|
170
|
-
const content = fs.readFileSync(claudeMdPath, 'utf-8');
|
|
171
|
-
const start = content.indexOf(CONSTRAINTS_START_MARKER);
|
|
172
|
-
const end = content.indexOf(CONSTRAINTS_END_MARKER) + CONSTRAINTS_END_MARKER.length + 1;
|
|
173
|
-
const actual = content.slice(start, end);
|
|
174
|
-
const version = /<!-- version: ([^ ]+) -->/.exec(content)![1];
|
|
175
|
-
// P6 漂移校验同款比对:期望段(生效集渲染) vs 实际段
|
|
176
|
-
expect(actual).toBe(renderConstraintsSection(getEffectiveConstraints(tempDir), version));
|
|
177
|
-
});
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
describe('setupClaudeMdOutputStyle(标记化)', () => {
|
|
181
|
-
let tempDir: string;
|
|
182
|
-
let claudeMdPath: string;
|
|
183
|
-
|
|
184
|
-
beforeEach(() => {
|
|
185
|
-
tempDir = fs.mkdtempSync(path.join(process.cwd(), 'temp-test-output-style-'));
|
|
186
|
-
claudeMdPath = path.join(tempDir, 'CLAUDE.md');
|
|
187
|
-
jest.spyOn(console, 'log').mockImplementation(() => {});
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
afterEach(() => {
|
|
191
|
-
jest.restoreAllMocks();
|
|
192
|
-
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
it('CLAUDE.md 不存在时不创建(由 constraints 注入负责创建)', async () => {
|
|
196
|
-
await setupClaudeMdOutputStyle(tempDir, io);
|
|
197
|
-
expect(fs.existsSync(claudeMdPath)).toBe(false);
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
it('无 Output Style 段:插入带标记段到文件顶部,二次运行幂等', async () => {
|
|
201
|
-
fs.writeFileSync(claudeMdPath, '# My Project\n\n正文\n');
|
|
202
|
-
await setupClaudeMdOutputStyle(tempDir, io);
|
|
203
|
-
|
|
204
|
-
const content = fs.readFileSync(claudeMdPath, 'utf-8');
|
|
205
|
-
expect(content.startsWith(OUTPUT_STYLE_START)).toBe(true);
|
|
206
|
-
expect(content).toContain('## Output Style');
|
|
207
|
-
expect(content).toContain(OUTPUT_STYLE_END);
|
|
208
|
-
expect(content).toContain('# My Project');
|
|
209
|
-
|
|
210
|
-
await setupClaudeMdOutputStyle(tempDir, io);
|
|
211
|
-
expect(fs.readFileSync(claudeMdPath, 'utf-8')).toBe(content);
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
it('旧版 harness 无标记段(特征串匹配):迁移为标记版', async () => {
|
|
215
|
-
const legacy = [
|
|
216
|
-
'## Output Style',
|
|
217
|
-
'',
|
|
218
|
-
'Terse like caveman. Technical substance exact. Only fluff die.',
|
|
219
|
-
'Drop: articles, filler (just/really/basically), pleasantries (sure/certainly/happy to), hedging.',
|
|
220
|
-
'',
|
|
221
|
-
'# My Project',
|
|
222
|
-
'',
|
|
223
|
-
].join('\n');
|
|
224
|
-
fs.writeFileSync(claudeMdPath, legacy);
|
|
225
|
-
|
|
226
|
-
await setupClaudeMdOutputStyle(tempDir, io);
|
|
227
|
-
const content = fs.readFileSync(claudeMdPath, 'utf-8');
|
|
228
|
-
|
|
229
|
-
expect(content).toContain(OUTPUT_STYLE_START);
|
|
230
|
-
expect(content).toContain(OUTPUT_STYLE_END);
|
|
231
|
-
expect(content).toContain('Terse like caveman');
|
|
232
|
-
expect(content).toContain('# My Project');
|
|
233
|
-
// 迁移后只剩一个 Output Style 标题
|
|
234
|
-
expect(content.match(/^## Output Style$/gm)).toHaveLength(1);
|
|
235
|
-
// 幂等
|
|
236
|
-
await setupClaudeMdOutputStyle(tempDir, io);
|
|
237
|
-
expect(fs.readFileSync(claudeMdPath, 'utf-8')).toBe(content);
|
|
238
|
-
});
|
|
239
|
-
|
|
240
|
-
it('用户自写 Output Style 段(特征串不匹配):不动、不追加、console 提示', async () => {
|
|
241
|
-
const userContent = '# My Project\n\n## Output Style\n\n用户自己的输出风格要求。\n';
|
|
242
|
-
fs.writeFileSync(claudeMdPath, userContent);
|
|
243
|
-
|
|
244
|
-
await setupClaudeMdOutputStyle(tempDir, io);
|
|
245
|
-
|
|
246
|
-
expect(fs.readFileSync(claudeMdPath, 'utf-8')).toBe(userContent);
|
|
247
|
-
expect(io.outText()).toContain('跳过');
|
|
248
|
-
});
|
|
249
|
-
});
|
|
250
|
-
|
|
251
|
-
const GOVERNANCE_PRESERVE_BEGIN = '<!-- PRESERVE:governance -->';
|
|
252
|
-
const GOVERNANCE_PRESERVE_END = '<!-- /PRESERVE:governance -->';
|
|
253
|
-
|
|
254
|
-
describe('setupAgentsMdConstraints(新落点模型:治理契约 → AGENTS.md PRESERVE:governance 段)', () => {
|
|
255
|
-
let tempDir: string;
|
|
256
|
-
let agentsMdPath: string;
|
|
257
|
-
|
|
258
|
-
beforeEach(() => {
|
|
259
|
-
tempDir = fs.mkdtempSync(path.join(process.cwd(), 'temp-test-init-agents-'));
|
|
260
|
-
agentsMdPath = path.join(tempDir, 'AGENTS.md');
|
|
261
|
-
jest.spyOn(console, 'log').mockImplementation(() => {});
|
|
262
|
-
});
|
|
263
|
-
|
|
264
|
-
afterEach(() => {
|
|
265
|
-
jest.restoreAllMocks();
|
|
266
|
-
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
267
|
-
});
|
|
268
|
-
|
|
269
|
-
it('AGENTS.md 不存在时创建骨架并写入 PRESERVE:governance 段', async () => {
|
|
270
|
-
await setupAgentsMdConstraints(tempDir, io);
|
|
271
|
-
|
|
272
|
-
const content = fs.readFileSync(agentsMdPath, 'utf-8');
|
|
273
|
-
expect(content).toContain('# AGENTS.md');
|
|
274
|
-
expect(content).toContain(GOVERNANCE_PRESERVE_BEGIN);
|
|
275
|
-
expect(content).toContain(GOVERNANCE_PRESERVE_END);
|
|
276
|
-
expect(content).toContain('## Governance Rules');
|
|
277
|
-
expect(content).toContain(CONSTRAINTS_START_MARKER);
|
|
278
|
-
expect(content).toContain(CONSTRAINTS_END_MARKER);
|
|
279
|
-
expect(content).toContain('### Iron Laws');
|
|
280
|
-
// PRESERVE 标记在约束段之外(sync-docs 重新生成时整段保留)
|
|
281
|
-
expect(content.indexOf(GOVERNANCE_PRESERVE_BEGIN)).toBeLessThan(content.indexOf(CONSTRAINTS_START_MARKER));
|
|
282
|
-
expect(content.indexOf(CONSTRAINTS_END_MARKER)).toBeLessThan(content.indexOf(GOVERNANCE_PRESERVE_END));
|
|
283
|
-
});
|
|
284
|
-
|
|
285
|
-
it('连跑两次输出一致(幂等)', async () => {
|
|
286
|
-
await setupAgentsMdConstraints(tempDir, io);
|
|
287
|
-
const first = fs.readFileSync(agentsMdPath, 'utf-8');
|
|
288
|
-
await setupAgentsMdConstraints(tempDir, io);
|
|
289
|
-
expect(fs.readFileSync(agentsMdPath, 'utf-8')).toBe(first);
|
|
290
|
-
});
|
|
291
|
-
|
|
292
|
-
it('已有 PRESERVE:governance 段(含标记+段内手写内容):只换标记区间,手写内容原样保留', async () => {
|
|
293
|
-
// 回归:曾把 PRESERVE 段内 HARNESS 标记之外的手写内容(治理契约引言/流程等)清空
|
|
294
|
-
fs.writeFileSync(agentsMdPath, [
|
|
295
|
-
'# AGENTS.md',
|
|
296
|
-
'',
|
|
297
|
-
'机器生成导读(保持不变)',
|
|
298
|
-
'',
|
|
299
|
-
GOVERNANCE_PRESERVE_BEGIN,
|
|
300
|
-
'## Governance Rules',
|
|
301
|
-
CONSTRAINTS_START_MARKER,
|
|
302
|
-
'<!-- version: 0.0.0 -->',
|
|
303
|
-
'过期的旧约束内容',
|
|
304
|
-
CONSTRAINTS_END_MARKER,
|
|
305
|
-
'',
|
|
306
|
-
'治理契约引言(手写,保留)',
|
|
307
|
-
'',
|
|
308
|
-
'### 治理变更流程(手写,保留)',
|
|
309
|
-
GOVERNANCE_PRESERVE_END,
|
|
310
|
-
'',
|
|
311
|
-
'<!-- PRESERVE:other -->',
|
|
312
|
-
'另一个手写段(保持不动)',
|
|
313
|
-
'<!-- /PRESERVE:other -->',
|
|
314
|
-
'',
|
|
315
|
-
].join('\n'));
|
|
316
|
-
|
|
317
|
-
await setupAgentsMdConstraints(tempDir, io);
|
|
318
|
-
|
|
319
|
-
const content = fs.readFileSync(agentsMdPath, 'utf-8');
|
|
320
|
-
expect(content).toContain('机器生成导读(保持不变)');
|
|
321
|
-
expect(content).toContain('另一个手写段(保持不动)');
|
|
322
|
-
expect(content).not.toContain('过期的旧约束内容');
|
|
323
|
-
expect(content).toContain(CONSTRAINTS_START_MARKER);
|
|
324
|
-
// 段内标记之外的手写内容原样保留
|
|
325
|
-
expect(content).toContain('治理契约引言(手写,保留)');
|
|
326
|
-
expect(content).toContain('### 治理变更流程(手写,保留)');
|
|
327
|
-
expect(content.indexOf(CONSTRAINTS_END_MARKER)).toBeLessThan(content.indexOf('治理契约引言(手写,保留)'));
|
|
328
|
-
expect(content.indexOf('### 治理变更流程(手写,保留)')).toBeLessThan(content.indexOf(GOVERNANCE_PRESERVE_END));
|
|
329
|
-
// 幂等
|
|
330
|
-
await setupAgentsMdConstraints(tempDir, io);
|
|
331
|
-
expect(fs.readFileSync(agentsMdPath, 'utf-8')).toBe(content);
|
|
332
|
-
});
|
|
333
|
-
|
|
334
|
-
it('已有 PRESERVE:governance 段但无 HARNESS 标记(纯手写段):段尾追加注入段,手写内容不动', async () => {
|
|
335
|
-
fs.writeFileSync(agentsMdPath, [
|
|
336
|
-
'# AGENTS.md',
|
|
337
|
-
'',
|
|
338
|
-
GOVERNANCE_PRESERVE_BEGIN,
|
|
339
|
-
'治理契约引言(手写,保留)',
|
|
340
|
-
'',
|
|
341
|
-
'### 发布纪律(手写,保留)',
|
|
342
|
-
GOVERNANCE_PRESERVE_END,
|
|
343
|
-
'',
|
|
344
|
-
].join('\n'));
|
|
345
|
-
|
|
346
|
-
await setupAgentsMdConstraints(tempDir, io);
|
|
347
|
-
|
|
348
|
-
const content = fs.readFileSync(agentsMdPath, 'utf-8');
|
|
349
|
-
expect(content).toContain('治理契约引言(手写,保留)');
|
|
350
|
-
expect(content).toContain('### 发布纪律(手写,保留)');
|
|
351
|
-
expect(content).toContain(CONSTRAINTS_START_MARKER);
|
|
352
|
-
// 注入段追加在手写内容之后、PRESERVE 段内
|
|
353
|
-
expect(content.indexOf('### 发布纪律(手写,保留)')).toBeLessThan(content.indexOf(CONSTRAINTS_START_MARKER));
|
|
354
|
-
expect(content.indexOf(CONSTRAINTS_END_MARKER)).toBeLessThan(content.indexOf(GOVERNANCE_PRESERVE_END));
|
|
355
|
-
// 幂等
|
|
356
|
-
await setupAgentsMdConstraints(tempDir, io);
|
|
357
|
-
expect(fs.readFileSync(agentsMdPath, 'utf-8')).toBe(content);
|
|
358
|
-
});
|
|
359
|
-
|
|
360
|
-
it('AGENTS.md 存在但无 PRESERVE:governance 段:末尾追加,既有内容不动', async () => {
|
|
361
|
-
fs.writeFileSync(agentsMdPath, '# AGENTS.md\n\n既有导读内容\n');
|
|
362
|
-
|
|
363
|
-
await setupAgentsMdConstraints(tempDir, io);
|
|
364
|
-
|
|
365
|
-
const content = fs.readFileSync(agentsMdPath, 'utf-8');
|
|
366
|
-
expect(content).toContain('既有导读内容');
|
|
367
|
-
expect(content).toContain(GOVERNANCE_PRESERVE_BEGIN);
|
|
368
|
-
expect(content.indexOf('既有导读内容')).toBeLessThan(content.indexOf(GOVERNANCE_PRESERVE_BEGIN));
|
|
369
|
-
});
|
|
370
|
-
|
|
371
|
-
it('PRESERVE:governance 标记残缺(只有单边):不写入并告警', async () => {
|
|
372
|
-
const original = `# AGENTS.md\n\n${GOVERNANCE_PRESERVE_BEGIN}\n残缺的段\n`;
|
|
373
|
-
fs.writeFileSync(agentsMdPath, original);
|
|
374
|
-
|
|
375
|
-
await setupAgentsMdConstraints(tempDir, io);
|
|
376
|
-
|
|
377
|
-
expect(fs.readFileSync(agentsMdPath, 'utf-8')).toBe(original);
|
|
378
|
-
expect(io.outText()).toContain('残缺');
|
|
379
|
-
});
|
|
380
|
-
|
|
381
|
-
it('PRESERVE 段内 HARNESS 标记残缺(单边):拒写并告警(候选1 守护,旧行为会再追加一份注入段)', async () => {
|
|
382
|
-
const broken = [
|
|
383
|
-
'# AGENTS.md',
|
|
384
|
-
'',
|
|
385
|
-
GOVERNANCE_PRESERVE_BEGIN,
|
|
386
|
-
'手写契约',
|
|
387
|
-
'',
|
|
388
|
-
CONSTRAINTS_START_MARKER,
|
|
389
|
-
'半残注入',
|
|
390
|
-
GOVERNANCE_PRESERVE_END,
|
|
391
|
-
'',
|
|
392
|
-
].join('\n');
|
|
393
|
-
fs.writeFileSync(agentsMdPath, broken);
|
|
394
|
-
await setupAgentsMdConstraints(tempDir, io);
|
|
395
|
-
expect(fs.readFileSync(agentsMdPath, 'utf-8')).toBe(broken);
|
|
396
|
-
expect(io.outText()).toContain('残缺');
|
|
397
|
-
});
|
|
398
|
-
|
|
399
|
-
it('段内注入文本与 renderConstraintsSection(getEffectiveConstraints()) 一致', async () => {
|
|
400
|
-
await setupAgentsMdConstraints(tempDir, io);
|
|
401
|
-
|
|
402
|
-
const content = fs.readFileSync(agentsMdPath, 'utf-8');
|
|
403
|
-
const start = content.indexOf(CONSTRAINTS_START_MARKER);
|
|
404
|
-
const end = content.indexOf(CONSTRAINTS_END_MARKER) + CONSTRAINTS_END_MARKER.length + 1;
|
|
405
|
-
const actual = content.slice(start, end);
|
|
406
|
-
const version = /<!-- version: ([^ ]+) -->/.exec(content)![1];
|
|
407
|
-
expect(actual).toBe(renderConstraintsSection(getEffectiveConstraints(tempDir), version));
|
|
408
|
-
});
|
|
409
|
-
});
|
|
410
|
-
|
|
411
|
-
describe('setupGovernanceConstraints(落点路由)', () => {
|
|
412
|
-
let tempDir: string;
|
|
413
|
-
|
|
414
|
-
beforeEach(() => {
|
|
415
|
-
tempDir = fs.mkdtempSync(path.join(process.cwd(), 'temp-test-init-routing-'));
|
|
416
|
-
jest.spyOn(console, 'log').mockImplementation(() => {});
|
|
417
|
-
});
|
|
418
|
-
|
|
419
|
-
afterEach(() => {
|
|
420
|
-
jest.restoreAllMocks();
|
|
421
|
-
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
422
|
-
});
|
|
423
|
-
|
|
424
|
-
it('新仓(无 CLAUDE.md):治理契约写向 AGENTS.md,不创建 CLAUDE.md', async () => {
|
|
425
|
-
await setupGovernanceConstraints(tempDir, io);
|
|
426
|
-
|
|
427
|
-
expect(fs.existsSync(path.join(tempDir, 'AGENTS.md'))).toBe(true);
|
|
428
|
-
expect(fs.existsSync(path.join(tempDir, 'CLAUDE.md'))).toBe(false);
|
|
429
|
-
const content = fs.readFileSync(path.join(tempDir, 'AGENTS.md'), 'utf-8');
|
|
430
|
-
expect(content).toContain(GOVERNANCE_PRESERVE_BEGIN);
|
|
431
|
-
expect(content).toContain(CONSTRAINTS_START_MARKER);
|
|
432
|
-
});
|
|
433
|
-
|
|
434
|
-
it('旧模型仓(CLAUDE.md 已有 HARNESS_CONSTRAINTS 标记):继续写 CLAUDE.md,不动 AGENTS.md', async () => {
|
|
435
|
-
fs.writeFileSync(path.join(tempDir, 'CLAUDE.md'), [
|
|
436
|
-
'# CLAUDE.md',
|
|
437
|
-
'',
|
|
438
|
-
'## Governance Rules',
|
|
439
|
-
CONSTRAINTS_START_MARKER,
|
|
440
|
-
'<!-- version: 0.0.0 -->',
|
|
441
|
-
'旧约束',
|
|
442
|
-
CONSTRAINTS_END_MARKER,
|
|
443
|
-
'',
|
|
444
|
-
].join('\n'));
|
|
445
|
-
fs.writeFileSync(path.join(tempDir, 'AGENTS.md'), '# AGENTS.md\n\n既有导读\n');
|
|
446
|
-
|
|
447
|
-
await setupGovernanceConstraints(tempDir, io);
|
|
448
|
-
|
|
449
|
-
const claude = fs.readFileSync(path.join(tempDir, 'CLAUDE.md'), 'utf-8');
|
|
450
|
-
expect(claude).not.toContain('旧约束');
|
|
451
|
-
expect(claude).toContain('### Iron Laws');
|
|
452
|
-
// AGENTS.md 不被写入治理段
|
|
453
|
-
expect(fs.readFileSync(path.join(tempDir, 'AGENTS.md'), 'utf-8')).toBe('# AGENTS.md\n\n既有导读\n');
|
|
454
|
-
});
|
|
455
|
-
|
|
456
|
-
it('旧模型仓(CLAUDE.md 有无标记 Governance Rules 块):路由到 CLAUDE.md', async () => {
|
|
457
|
-
fs.writeFileSync(path.join(tempDir, 'CLAUDE.md'), '# CLAUDE.md\n\n## Governance Rules\n\n手写条款\n');
|
|
458
|
-
|
|
459
|
-
await setupGovernanceConstraints(tempDir, io);
|
|
460
|
-
|
|
461
|
-
const claude = fs.readFileSync(path.join(tempDir, 'CLAUDE.md'), 'utf-8');
|
|
462
|
-
expect(claude).toContain('手写条款');
|
|
463
|
-
expect(claude).toContain(CONSTRAINTS_START_MARKER);
|
|
464
|
-
expect(fs.existsSync(path.join(tempDir, 'AGENTS.md'))).toBe(false);
|
|
465
|
-
});
|
|
466
|
-
});
|