@dommaker/harness 1.4.0 → 1.5.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 +12 -0
- package/dist/cli/commands/check.d.ts.map +1 -1
- package/dist/cli/commands/check.js +28 -0
- package/dist/cli/commands/check.js.map +1 -1
- package/dist/cli/commands/definitions.d.ts.map +1 -1
- package/dist/cli/commands/definitions.js +0 -7
- package/dist/cli/commands/definitions.js.map +1 -1
- package/dist/cli/commands/knowledge.d.ts +0 -19
- package/dist/cli/commands/knowledge.d.ts.map +1 -1
- package/dist/cli/commands/knowledge.js +0 -105
- package/dist/cli/commands/knowledge.js.map +1 -1
- package/dist/cli/commands/update-user-model.d.ts +7 -2
- package/dist/cli/commands/update-user-model.d.ts.map +1 -1
- package/dist/cli/commands/update-user-model.js +25 -16
- package/dist/cli/commands/update-user-model.js.map +1 -1
- package/dist/core/constraints/checker.d.ts.map +1 -1
- package/dist/core/constraints/checker.js +5 -3
- package/dist/core/constraints/checker.js.map +1 -1
- package/dist/core/constraints/checkers/capability-sync.d.ts +17 -6
- package/dist/core/constraints/checkers/capability-sync.d.ts.map +1 -1
- package/dist/core/constraints/checkers/capability-sync.js +48 -19
- package/dist/core/constraints/checkers/capability-sync.js.map +1 -1
- package/dist/core/constraints/checkers/docs-freshness.d.ts.map +1 -1
- package/dist/core/constraints/checkers/docs-freshness.js +15 -6
- package/dist/core/constraints/checkers/docs-freshness.js.map +1 -1
- package/dist/core/constraints/checkers/index.d.ts +2 -2
- package/dist/core/constraints/checkers/index.d.ts.map +1 -1
- package/dist/core/constraints/checkers/index.js +2 -1
- package/dist/core/constraints/checkers/index.js.map +1 -1
- package/dist/core/constraints/checkers/types.d.ts +41 -2
- package/dist/core/constraints/checkers/types.d.ts.map +1 -1
- package/dist/core/constraints/checkers/types.js +33 -0
- package/dist/core/constraints/checkers/types.js.map +1 -1
- package/dist/gates/checker-gate.d.ts +2 -1
- package/dist/gates/checker-gate.d.ts.map +1 -1
- package/dist/gates/checker-gate.js +10 -7
- package/dist/gates/checker-gate.js.map +1 -1
- package/dist/types/constraint.d.ts +8 -0
- package/dist/types/constraint.d.ts.map +1 -1
- package/dist/types/constraint.js +6 -1
- package/dist/types/constraint.js.map +1 -1
- package/dist/types/trace.d.ts +9 -0
- package/dist/types/trace.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/CONTEXT.md +1 -0
- package/src/__tests__/passes-gate.test.ts +11 -18
- package/src/cli/commands/CONTEXT.md +2 -2
- package/src/cli/commands/__tests__/bin-exit-mapping.test.ts +6 -0
- package/src/cli/commands/__tests__/check.test.ts +74 -0
- package/src/cli/commands/__tests__/project-path-convention.test.ts +85 -23
- package/src/cli/commands/__tests__/registry.test.ts +6 -0
- package/src/cli/commands/__tests__/update-user-model.test.ts +35 -1
- package/src/cli/commands/check.ts +39 -1
- package/src/cli/commands/definitions.ts +0 -7
- package/src/cli/commands/knowledge.ts +0 -119
- package/src/cli/commands/update-user-model.ts +31 -16
- package/src/core/CONTEXT.md +2 -1
- package/src/core/constraints/checker.ts +8 -4
- package/src/core/constraints/checkers/__tests__/capability-sync.test.ts +90 -31
- package/src/core/constraints/checkers/__tests__/check-outcome.test.ts +176 -0
- package/src/core/constraints/checkers/__tests__/docs-freshness.test.ts +18 -9
- package/src/core/constraints/checkers/__tests__/three-state-consistency.test.ts +12 -4
- package/src/core/constraints/checkers/capability-sync.ts +56 -18
- package/src/core/constraints/checkers/docs-freshness.ts +18 -5
- package/src/core/constraints/checkers/index.ts +9 -2
- package/src/core/constraints/checkers/types.ts +58 -2
- package/src/gates/CONTEXT.md +1 -1
- package/src/gates/__tests__/checker-gate.test.ts +24 -0
- package/src/gates/checker-gate.ts +11 -15
- package/src/types/constraint.ts +16 -2
- package/src/types/trace.ts +10 -0
|
@@ -6,7 +6,10 @@
|
|
|
6
6
|
* 通过 ConstraintCheck 接口 + CheckEnv 环境与编排层解耦。
|
|
7
7
|
*/
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.MAX_EVIDENCE_ITEMS = void 0;
|
|
9
10
|
exports.buildCheckEnv = buildCheckEnv;
|
|
11
|
+
exports.formatEvidence = formatEvidence;
|
|
12
|
+
exports.normalizeCheckOutcome = normalizeCheckOutcome;
|
|
10
13
|
exports.contextFlag = contextFlag;
|
|
11
14
|
exports.contextEvidenceFlag = contextEvidenceFlag;
|
|
12
15
|
/**
|
|
@@ -32,6 +35,36 @@ function buildCheckEnv(context, evidence) {
|
|
|
32
35
|
}
|
|
33
36
|
return { context, projectPath, ...evidence };
|
|
34
37
|
}
|
|
38
|
+
/** 证据条数上限:trace 是 JSONL,缺口上百项时不能整仓落盘 */
|
|
39
|
+
exports.MAX_EVIDENCE_ITEMS = 10;
|
|
40
|
+
/**
|
|
41
|
+
* 组装证据行:一行说明 + 逐条依据(超限截断)
|
|
42
|
+
*
|
|
43
|
+
* 每行自描述且不带缩进——缩进由消费端(CLI / gate 理由 / 铁律异常文案)决定,trace 原样存。
|
|
44
|
+
* 修复入口不写在这里:那是各约束自己的事,写进各自的 summary 行(docs_freshness 的
|
|
45
|
+
* 幽灵条目与 sync-docs 无关,共用一个指针会误导)。
|
|
46
|
+
*/
|
|
47
|
+
function formatEvidence(summary, items) {
|
|
48
|
+
const shown = items.slice(0, exports.MAX_EVIDENCE_ITEMS);
|
|
49
|
+
const hidden = items.length - shown.length;
|
|
50
|
+
return [...(summary ? [summary] : []), ...shown, ...(hidden > 0 ? [`…另 ${hidden} 项`] : [])];
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* 归一 CheckOutcome(boolean | 'skip' | CheckDetail)为统一形状
|
|
54
|
+
*
|
|
55
|
+
* skip 恒不携带证据:未评估的约束不得留下证据行(否则统计侧会把「没查」读成「查出问题」)。
|
|
56
|
+
*/
|
|
57
|
+
function normalizeCheckOutcome(outcome) {
|
|
58
|
+
if (outcome === 'skip')
|
|
59
|
+
return { satisfied: true, skipped: true, evidence: [] };
|
|
60
|
+
if (typeof outcome === 'boolean')
|
|
61
|
+
return { satisfied: outcome, skipped: false, evidence: [] };
|
|
62
|
+
return {
|
|
63
|
+
satisfied: outcome.pass,
|
|
64
|
+
skipped: false,
|
|
65
|
+
evidence: outcome.evidence ?? [],
|
|
66
|
+
};
|
|
67
|
+
}
|
|
35
68
|
/**
|
|
36
69
|
* 构造纯上下文标志检查(无 I/O 的轻量约束)
|
|
37
70
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../src/core/constraints/checkers/types.ts"],"names":[],"mappings":";AAAA;;;;;GAKG
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../src/core/constraints/checkers/types.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;;AAgDH,sCAeC;AA6CD,wCAIC;AAOD,sDAQC;AAeD,kCAKC;AASD,kDAYC;AAlID;;;;;;;;;GASG;AACH,SAAgB,aAAa,CAC3B,OAA0B,EAC1B,QAAoC;IAEpC,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;IACzD,IAAI,QAAQ,KAAK,MAAM,EAAE,CAAC;QACxB,OAAO;YACL,OAAO;YACP,WAAW;YACX,UAAU,EAAE,KAAK,IAAI,EAAE,CAAC,EAAE;YAC1B,eAAe,EAAE,KAAK,IAAI,EAAE,CAAC,EAAE;YAC/B,OAAO,EAAE,GAAG,EAAE,CAAC,EAAE;SAClB,CAAC;IACJ,CAAC;IACD,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,GAAG,QAAQ,EAAE,CAAC;AAC/C,CAAC;AAmCD,wCAAwC;AAC3B,QAAA,kBAAkB,GAAG,EAAE,CAAC;AAErC;;;;;;GAMG;AACH,SAAgB,cAAc,CAAC,OAAe,EAAE,KAAe;IAC7D,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,0BAAkB,CAAC,CAAC;IACjD,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC;IAC3C,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,KAAK,EAAE,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,MAAM,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AAC9F,CAAC;AAED;;;;GAIG;AACH,SAAgB,qBAAqB,CAAC,OAAqB;IACzD,IAAI,OAAO,KAAK,MAAM;QAAE,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;IAChF,IAAI,OAAO,OAAO,KAAK,SAAS;QAAE,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;IAC9F,OAAO;QACL,SAAS,EAAE,OAAO,CAAC,IAAI;QACvB,OAAO,EAAE,KAAK;QACd,QAAQ,EAAE,OAAO,CAAC,QAAQ,IAAI,EAAE;KACjC,CAAC;AACJ,CAAC;AAYD;;GAEG;AACH,SAAgB,WAAW,CACzB,EAAU,EACV,SAAkD;IAElD,OAAO,EAAE,EAAE,EAAE,QAAQ,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,SAAS,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;AAC3D,CAAC;AAED;;;;;;GAMG;AACH,SAAgB,mBAAmB,CACjC,EAAU,EACV,IAAyD;IAEzD,OAAO;QACL,EAAE;QACF,QAAQ,EAAE,CAAC,GAAG,EAAE,EAAE;YAChB,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;YAChC,IAAI,KAAK,KAAK,SAAS;gBAAE,OAAO,MAAM,CAAC;YACvC,OAAO,KAAK,CAAC;QACf,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -11,7 +11,8 @@ import type { Gate } from './types';
|
|
|
11
11
|
/**
|
|
12
12
|
* 将已注册 checker 适配为统一 Gate(checker-as-guard 接线点)
|
|
13
13
|
*
|
|
14
|
-
* 判定映射:false
|
|
14
|
+
* 判定映射:violated(false 或 CheckDetail.pass=false)→ deny;
|
|
15
|
+
* 满足 / 'skip'(未评估)→ abstain。违规证据随理由带出。
|
|
15
16
|
*
|
|
16
17
|
* @param check 已注册的 ConstraintCheck(经 checkers 注册表取回)
|
|
17
18
|
* @param order 门禁顺序(供 guard 链排序)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"checker-gate.d.ts","sourceRoot":"","sources":["../../src/gates/checker-gate.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAEpE,OAAO,KAAK,EAAE,IAAI,EAA6B,MAAM,SAAS,CAAC;AAI/D
|
|
1
|
+
{"version":3,"file":"checker-gate.d.ts","sourceRoot":"","sources":["../../src/gates/checker-gate.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAEpE,OAAO,KAAK,EAAE,IAAI,EAA6B,MAAM,SAAS,CAAC;AAI/D;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,eAAe,EAAE,KAAK,SAAI,GAAG,IAAI,CAkBzE"}
|
|
@@ -15,7 +15,8 @@ const decision_1 = require("./decision");
|
|
|
15
15
|
/**
|
|
16
16
|
* 将已注册 checker 适配为统一 Gate(checker-as-guard 接线点)
|
|
17
17
|
*
|
|
18
|
-
* 判定映射:false
|
|
18
|
+
* 判定映射:violated(false 或 CheckDetail.pass=false)→ deny;
|
|
19
|
+
* 满足 / 'skip'(未评估)→ abstain。违规证据随理由带出。
|
|
19
20
|
*
|
|
20
21
|
* @param check 已注册的 ConstraintCheck(经 checkers 注册表取回)
|
|
21
22
|
* @param order 门禁顺序(供 guard 链排序)
|
|
@@ -28,12 +29,14 @@ function createCheckerGate(check, order = 0) {
|
|
|
28
29
|
const startTime = Date.now();
|
|
29
30
|
const projectPath = ctx.projectPath || process.cwd();
|
|
30
31
|
const env = (0, checkers_1.buildCheckEnv)({ operation: 'manual', projectPath }, 'none');
|
|
31
|
-
const outcome = await check.evaluate(env);
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
32
|
+
const outcome = (0, checkers_1.normalizeCheckOutcome)(await check.evaluate(env));
|
|
33
|
+
const evidence = outcome.evidence.map((e) => `\n - ${e}`).join('');
|
|
34
|
+
const reason = outcome.skipped
|
|
35
|
+
? `checker "${check.id}" 跳过(证据未接线)`
|
|
36
|
+
: outcome.satisfied
|
|
37
|
+
? `checker "${check.id}" 通过`
|
|
38
|
+
: `checker "${check.id}" 判定违规${evidence}`;
|
|
39
|
+
return (0, decision_1.decisionFromResult)((0, types_1.gateResult)(check.id, outcome.satisfied, reason, startTime));
|
|
37
40
|
},
|
|
38
41
|
};
|
|
39
42
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"checker-gate.js","sourceRoot":"","sources":["../../src/gates/checker-gate.ts"],"names":[],"mappings":";AAAA;;;;;;;GAOG;;
|
|
1
|
+
{"version":3,"file":"checker-gate.js","sourceRoot":"","sources":["../../src/gates/checker-gate.ts"],"names":[],"mappings":";AAAA;;;;;;;GAOG;;AAiBH,8CAkBC;AAhCD,2DAAoF;AAEpF,mCAAqC;AACrC,yCAAgD;AAEhD;;;;;;;;GAQG;AACH,SAAgB,iBAAiB,CAAC,KAAsB,EAAE,KAAK,GAAG,CAAC;IACjE,OAAO;QACL,EAAE,EAAE,KAAK,CAAC,EAAE;QACZ,KAAK;QACL,KAAK,CAAC,QAAQ,CAAC,GAAgB;YAC7B,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YAC7B,MAAM,WAAW,GAAG,GAAG,CAAC,WAAW,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;YACrD,MAAM,GAAG,GAAG,IAAA,wBAAa,EAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,WAAW,EAAE,EAAE,MAAM,CAAC,CAAC;YACxE,MAAM,OAAO,GAAG,IAAA,gCAAqB,EAAC,MAAM,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC;YACjE,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YACpE,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO;gBAC5B,CAAC,CAAC,YAAY,KAAK,CAAC,EAAE,aAAa;gBACnC,CAAC,CAAC,OAAO,CAAC,SAAS;oBACjB,CAAC,CAAC,YAAY,KAAK,CAAC,EAAE,MAAM;oBAC5B,CAAC,CAAC,YAAY,KAAK,CAAC,EAAE,SAAS,QAAQ,EAAE,CAAC;YAC9C,OAAO,IAAA,6BAAkB,EAAC,IAAA,kBAAU,EAAC,KAAK,CAAC,EAAE,EAAE,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC;QACxF,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -86,6 +86,14 @@ export interface ConstraintResult {
|
|
|
86
86
|
constraint?: Constraint;
|
|
87
87
|
/** 消息 */
|
|
88
88
|
message?: string;
|
|
89
|
+
/**
|
|
90
|
+
* 证据行(harness#119)
|
|
91
|
+
*
|
|
92
|
+
* checker 经 CheckDetail 返回的具体判定依据(如未登记的文件路径)。
|
|
93
|
+
* 违规时随 message 一并展示;满足但非空时为「提示」(不进 pass/fail 统计)。
|
|
94
|
+
* skip 恒为空——未评估不得留下证据行。
|
|
95
|
+
*/
|
|
96
|
+
evidence?: string[];
|
|
89
97
|
/** 建议操作 */
|
|
90
98
|
requiredAction?: string;
|
|
91
99
|
/** 检查时间 */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constraint.d.ts","sourceRoot":"","sources":["../../src/types/constraint.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"constraint.d.ts","sourceRoot":"","sources":["../../src/types/constraint.ts"],"names":[],"mappings":"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"}
|
package/dist/types/constraint.js
CHANGED
|
@@ -16,7 +16,12 @@ exports.ConstraintViolationError = void 0;
|
|
|
16
16
|
class ConstraintViolationError extends Error {
|
|
17
17
|
result;
|
|
18
18
|
constructor(result) {
|
|
19
|
-
|
|
19
|
+
// 铁律违规在 checkConstraints 处直接 throw,CLI 的结构化输出块走不到这里,
|
|
20
|
+
// error.message 是判定证据唯一的外溢面(harness#119)
|
|
21
|
+
const detail = result.evidence?.length
|
|
22
|
+
? `:\n${result.evidence.map((line) => ` - ${line}`).join('\n')}`
|
|
23
|
+
: '';
|
|
24
|
+
super(`${result.message || 'Constraint violation'}${detail}`);
|
|
20
25
|
this.name = 'ConstraintViolationError';
|
|
21
26
|
this.result = result;
|
|
22
27
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constraint.js","sourceRoot":"","sources":["../../src/types/constraint.ts"],"names":[],"mappings":";AAAA;;;;;;;;GAQG;;;
|
|
1
|
+
{"version":3,"file":"constraint.js","sourceRoot":"","sources":["../../src/types/constraint.ts"],"names":[],"mappings":";AAAA;;;;;;;;GAQG;;;AA8LH;;GAEG;AACH,MAAa,wBAAyB,SAAQ,KAAK;IACjC,MAAM,CAAmB;IAEzC,YAAY,MAAwB;QAClC,qDAAqD;QACrD,yCAAyC;QACzC,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,EAAE,MAAM;YACpC,CAAC,CAAC,MAAM,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;YACjE,CAAC,CAAC,EAAE,CAAC;QACP,KAAK,CAAC,GAAG,MAAM,CAAC,OAAO,IAAI,sBAAsB,GAAG,MAAM,EAAE,CAAC,CAAC;QAC9D,IAAI,CAAC,IAAI,GAAG,0BAA0B,CAAC;QACvC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;CACF;AAbD,4DAaC"}
|
package/dist/types/trace.d.ts
CHANGED
|
@@ -17,6 +17,8 @@ export declare const DEFAULT_TRACE_FILE = ".harness/logs/traces.log";
|
|
|
17
17
|
* - 只记录核心字段(零 Token)
|
|
18
18
|
* - 不记录代码片段(需要时从 git diff 获取)
|
|
19
19
|
* - 不记录决策路径(需要时从 execution logs 获取)
|
|
20
|
+
* - 例外(harness#119):判定证据允许记录——路径级依据不是代码片段,
|
|
21
|
+
* 而没有它,一条恒红的约束在统计侧根本无法回答「红在哪」
|
|
20
22
|
*/
|
|
21
23
|
export interface ExecutionTrace {
|
|
22
24
|
/** 约束 ID */
|
|
@@ -35,6 +37,13 @@ export interface ExecutionTrace {
|
|
|
35
37
|
projectPath?: string;
|
|
36
38
|
/** 会话 ID(用于追踪同一会话的多次检查) */
|
|
37
39
|
sessionId?: string;
|
|
40
|
+
/**
|
|
41
|
+
* 判定证据行(harness#119)
|
|
42
|
+
*
|
|
43
|
+
* 只落 checker 给出的路径级依据(如未登记文件清单),由 checker 截断条数;
|
|
44
|
+
* 不落 pass 结果的空数组,避免无信息记录膨胀 JSONL。
|
|
45
|
+
*/
|
|
46
|
+
evidence?: string[];
|
|
38
47
|
/** 用户如何响应失败 */
|
|
39
48
|
userAction?: 'bypass' | 'fix' | 'ignore' | 'request_help';
|
|
40
49
|
/** 用户绕过理由(如果用户绕过) */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"trace.d.ts","sourceRoot":"","sources":["../../src/types/trace.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,6BAA6B,CAAC;AAE7D
|
|
1
|
+
{"version":3,"file":"trace.d.ts","sourceRoot":"","sources":["../../src/types/trace.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/CONTEXT.md
CHANGED
|
@@ -42,6 +42,7 @@
|
|
|
42
42
|
| 回滚 | 分工:版本化回退(单段——yml 数据文件 git 版本化,删段恢复)+ 提案回滚(多段 inverse,挂 studio#82 D6) |
|
|
43
43
|
| 文件驱动 CLI | yml 状态真值 + 一次性进程、无常驻生命周期;故不引 dispose 链(止步档 1) |
|
|
44
44
|
| 对照(reconcile) | CAPABILITIES.md 声明与源码实况的双向一致性判定(代码→文档查漏登,文档→代码查幽灵);唯一实现 `core/constraints/capabilities-reconcile.ts`,capability_sync / docs_freshness / sync-docs 共消费(ADR-0009) |
|
|
45
|
+
| 判定证据(evidence) | checker 随判定一并返回的路径级依据(`CheckDetail{pass, evidence}`,harness#119/ADR-0016);文案由 checker 措辞、消费端只打印,出口 = `ConstraintResult` / CLI 输出 / trace / 铁律异常文案。不是第四种统计态:**违规** = 与本次变更有因果(进 pass/fail 分母),**提示** = `pass:true` + evidence(仓库级漂移等无因果缺口,只露出不拦) |
|
|
45
46
|
| 测试产物解读(test-output) | 从测试运行器 stdout 读出「过了没 / 哪些失败 / 覆盖率」的判定;唯一实现 `core/validators/test-output.ts`(包内,不进导出面),passes-gate / acceptance 共消费(ADR-0012)。两门禁判定依据不一致待 #93 裁决,覆盖率取数归属待 #94 裁决 |
|
|
46
47
|
| 飞轮指标(flywheel) | 知识条目的引用与消费度量(refCoverage / avgRefs / consumptionHitRate);唯一实现 `knowledge/flywheel-metrics.ts`(包内,不进导出面),canonical 分子为过滤 synthetic 后的 genuine refs,audit D6 / knowledge stats / knowledge health 共消费(ADR-0013,#81) |
|
|
47
48
|
| 测试夹具(project fixture) | 临时项目根 + 落盘声明;唯一实现 `test-setup/project-fixture.ts`(测试层,不进导出面)——`config` 槽是 `.harness/config.yml` 落点唯一正本(字符串原样落盘,畸形/脏配置用例依赖此口径),`traces` 槽/`writeProjectTraces` 是 trace 落盘唯一正本(路径锚定 `DEFAULT_TRACE_FILE`、序列化走 `appendJsonl` 生产写链,harness#108),其余文件走 `files` 相对路径;非缺省根必须经 `parentDir` **显式** opt-out(cwd 锚定用例语义),回收经 `mkdtemp-cleanup` 劫持(harness#90) |
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import { describe, it, expect, beforeAll, afterAll } from '@jest/globals';
|
|
6
6
|
import { PassesGate, createPassesGate, detectTestCommand } from '../core/validators/passes-gate';
|
|
7
7
|
import type { TaskTestResult } from '../types/passes-gate';
|
|
8
|
+
import { createProjectFixture } from '../test-setup/project-fixture';
|
|
8
9
|
import { mkdirSync, rmSync, writeFileSync } from 'fs';
|
|
9
10
|
import { join } from 'path';
|
|
10
11
|
|
|
@@ -224,24 +225,16 @@ describe('PassesGate', () => {
|
|
|
224
225
|
* (extractCoverage → TaskTestResult.coverage)整体删除,结果面不再有 coverage 字段。
|
|
225
226
|
*/
|
|
226
227
|
describe('覆盖率取数路删除(harness#94)', () => {
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
}
|
|
237
|
-
});
|
|
238
|
-
|
|
239
|
-
afterAll(() => {
|
|
240
|
-
try {
|
|
241
|
-
rmSync(covDir, { recursive: true, force: true });
|
|
242
|
-
} catch {
|
|
243
|
-
// ignore
|
|
244
|
-
}
|
|
228
|
+
// 临时根走 createProjectFixture 正本(tmpdir,mkdtemp 劫持统一回收),不建在仓内
|
|
229
|
+
const covDir = createProjectFixture({
|
|
230
|
+
name: 'pg-no-coverage',
|
|
231
|
+
files: {
|
|
232
|
+
// 输出里放一份 jest 覆盖率表:删除前 extractCoverage 会从中抠出 80.5 填进结果字段
|
|
233
|
+
'package.json': JSON.stringify({
|
|
234
|
+
name: 'coverage-project',
|
|
235
|
+
scripts: { test: 'echo "All files | 80.50 | 70.21 | 83.33 | 79.59 |"' },
|
|
236
|
+
}),
|
|
237
|
+
},
|
|
245
238
|
});
|
|
246
239
|
|
|
247
240
|
it('setPasses 结果不带 coverage 字段', async () => {
|
|
@@ -30,7 +30,7 @@ H5(#44)起:
|
|
|
30
30
|
- 命令选项类型命名规范:XxxOptions
|
|
31
31
|
- **本层是上行数据的注入方(harness#88)**:core 对 cli/gates/monitoring 零值导入,故 `check`/`report` 自己 `new ConstraintChecker(getTraceCollector())`(组合根接线 trace 记录器;用 `constraintChecker`/`getInstance()` 拿到的默认实例不写 trace),`sync-docs` 把 `COMMAND_DEFINITIONS` + `GATE_DEFINITIONS` 组装成 `CapabilityDefinitionSource` 注入 `capabilities-parser` 供能力清单计数
|
|
32
32
|
- **projectPath 只在入口兜底一次,并传到每个 IO/执行点(harness#95)**:`-p/--project-path` 的 cwd 兜底唯一落点是命令模块入口(`const projectPath = options.projectPath || process.cwd()`);往下每层只能收参数,**禁止再取一次 `process.cwd()`、也禁止用相对路径默认值**——两者都让 `-p` 半失效:执行/读写位置悄悄回到调用方 cwd,而 acceptance 的「无 tasks.yml 即跳过」本身就是 `passed:true`,于是失效表现为假绿。落地形状:执行/IO 根用**必传形参**表达(`PassesGate.runTests(workDir)`),门禁内的相对子路径锚到 projectPath:`ContractGate` 一直是 `path.join(context.projectPath, contractPath)`,`SpecAcceptanceGate` 的 tasksPath 随 #95 对齐成 `path.resolve(projectPath, 相对值)`(绝对值原样,缺省 `<projectPath>/tasks.yml`)
|
|
33
|
-
- 机器可检:`__tests__/project-path-convention.test.ts` 三道闸——CLI 层 cwd 必须是 `xxx || process.cwd()`
|
|
33
|
+
- 机器可检:`__tests__/project-path-convention.test.ts` 三道闸——CLI 层 cwd 必须是 `xxx || process.cwd()` 兜底形状、下游层(= src 减 cli)cwd 站点**逐行**冻结(#98,豁免文件内新增行/行变形同样失败)、相对路径默认值冻结(#98 收紧:`xxxPath: '相对值'` 对象字面量之外,参数默认值形/模板字面量/双引号形同罪,扫描域 = 整个下游层);豁免逐条带理由,站点消失却不删条目同样失败
|
|
34
34
|
- 行为可检:`__tests__/project-path-anchoring.test.ts` 前提统一 **cwd ≠ projectPath**、不 mock 任何 IO,断言测试真在 B 执行、证据落 `B/.harness/evidence/`、acceptance 读 `B/tasks.yml`。「根已传入却又取 cwd」这类静态看不出的漂移由它守,冻结表守的是不再新增站点
|
|
35
35
|
- **读到部分结果必须告知(harness#100)**:命令面以 `skip` 策略读 JSONL(traces.log / failures.log)时,坏行计数要到用户可见输出,**不允许默默丢弃**。共同约束:无损坏零噪声(计数为 0 不出行)、不改任何既有计数口径(`status` 的 `记录数` 仍是原始非空行数,逐字节不变)、计数一律取自 `src/utils/jsonl` 读链返回的 `skippedLines`(经报告入口透传或在读点就地取用),**禁止**用「原始行数 − 统计条数」反推(窗过滤掉的合法行与坏行分不开)。三处落点各自对得上一行代码:
|
|
36
36
|
- `status`:告知**走 stderr**(`logError(io, …)`),因为 stdout 是报告体、其字节已被 `记录数` 那一行定死,挤进去等于改输出
|
|
@@ -43,7 +43,7 @@ H5(#44)起:
|
|
|
43
43
|
- 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 报警)
|
|
44
44
|
- PRESERVE:governance 段内写入纪律:机器管理的只有 HARNESS_CONSTRAINTS 标记区间——有标记只换标记区间,无标记(纯手写段)在段尾追加注入段;段内其余手写内容(治理契约引言/流程/纪律等)必须原样保留(曾整段替换清空手写内容的回归,init-injection.test 有防回归用例)
|
|
45
45
|
- 注入段落点路由与 marker-range 替换收口在 `core/constraints/injection-writer`(ADR-0011):读侧 `resolveInjectionTarget`(漂移检测/retire 同步共用「CLAUDE.md 有标记优先,否则 AGENTS.md」)+ 写侧 `resolveGovernanceLanding`(init 落点选择);init 三个治理段 writer 与 retire 注入同步均为「渲染 body + 调 writer」,半标记(单边/乱序)一律拒写告警;未注入 = 两处均无完整标记段
|
|
46
|
-
- knowledge 命令包含
|
|
46
|
+
- knowledge 命令包含 11 个子操作(list/search/import/decay/stats/sync-rag/audit/snapshot/migrate/index/health);upsert/sync-status 已迁至 studio CLI(harness#110,二者硬编码 localhost Studio 内部端点,不属 harness「通用框架、文件驱动」定位)
|
|
47
47
|
- `stats` / `health` 的飞轮数字不在 CLI 内计算:分子口径唯一实现是 `knowledge/flywheel-metrics.ts`(`evaluateFlywheel`,ADR-0013),本层只做百分比取整/一位小数与字段名映射;`.consumption-stats.json` 的读取留在各命令(module 零 IO)
|
|
48
48
|
- 特殊路由(选项条件、子命令兜底、裸跑语义)表达在定义表的 optionRoutes / subcommands / subcommandStrict / bareRunsAction 字段,bin 是纯通用引擎、不含单命令知识
|
|
49
49
|
- **命令 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`
|
|
@@ -67,6 +67,12 @@ describe('命令实现/定义表目录零 process.exit(唯一出口在 bin)'
|
|
|
67
67
|
});
|
|
68
68
|
});
|
|
69
69
|
|
|
70
|
+
// CI 上 dist 缺失时显式失败而非静默 skip(harness#99):#86 退出语义的
|
|
71
|
+
// 唯一端到端证据在下面的 smoke 块,CI 必须先 build 再 test,否则主证据隐形。
|
|
72
|
+
if (!hasDist && process.env.CI) {
|
|
73
|
+
throw new Error('dist 未构建:bin 端到端 smoke 不得静默 skip —— CI 必须先 npm run build 再 npm test(harness#99)');
|
|
74
|
+
}
|
|
75
|
+
|
|
70
76
|
const smoke = hasDist ? describe : describe.skip;
|
|
71
77
|
|
|
72
78
|
smoke('端到端:真实命令经 bin 映射后的对外退出码', () => {
|
|
@@ -200,6 +200,8 @@ describe('check command(真 git fixture)', () => {
|
|
|
200
200
|
stageChange(
|
|
201
201
|
dir,
|
|
202
202
|
'src/existing.ts',
|
|
203
|
+
// 'pass'+'word' 拆串是躲本仓 no_hardcoded_credentials 静态扫描:测试源文件里出现完整
|
|
204
|
+
// `password = "..."` 字面量会撞自己的 pre-commit 门禁;运行时拼接值不变,断言不受影响
|
|
203
205
|
'export const a = 1;\nconst pass' + 'word = "correcthorsebattery";\n'
|
|
204
206
|
);
|
|
205
207
|
passTraces(dir);
|
|
@@ -240,6 +242,78 @@ describe('check command(真 git fixture)', () => {
|
|
|
240
242
|
});
|
|
241
243
|
});
|
|
242
244
|
|
|
245
|
+
describe('判定证据外显(harness#119)', () => {
|
|
246
|
+
/** 带 CAPABILITIES.md 的 git 仓库:登记列表外的源文件即为仓库级漂移 */
|
|
247
|
+
function capRepo(registered: string[]): string {
|
|
248
|
+
const dir = gitRepo();
|
|
249
|
+
const rows = registered
|
|
250
|
+
.map((rel) => `| ${path.basename(rel, '.ts')} | ${rel} | fixture |\n`)
|
|
251
|
+
.join('');
|
|
252
|
+
write(dir, 'CAPABILITIES.md', `# C\n\n| 模块 | 文件 | 说明 |\n|------|------|------|\n${rows}`);
|
|
253
|
+
git(dir, 'add', 'CAPABILITIES.md');
|
|
254
|
+
git(dir, 'commit', '-q', '-m', 'capabilities');
|
|
255
|
+
return dir;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/** 从本用例 trace 文件里取某条约束的记录 */
|
|
259
|
+
function traceOf(constraintId: string) {
|
|
260
|
+
return fs
|
|
261
|
+
.readFileSync(traceFile, 'utf-8')
|
|
262
|
+
.split('\n')
|
|
263
|
+
.filter(Boolean)
|
|
264
|
+
.map((line) => JSON.parse(line) as { constraintId: string; result: string; evidence?: string[] })
|
|
265
|
+
.find((l) => l.constraintId === constraintId);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
it('变更文件未登记 → 警告随附证据点名该文件', async () => {
|
|
269
|
+
const dir = capRepo(['src/existing.ts', 'src/nested/deep.ts']);
|
|
270
|
+
stageChange(dir, 'src/nested/extra.ts', 'export const e = 1;\n');
|
|
271
|
+
passTraces(dir);
|
|
272
|
+
|
|
273
|
+
const result = await check(
|
|
274
|
+
{ preset: 'standard', staged: true, projectPath: dir, trigger: 'module_modification' },
|
|
275
|
+
io
|
|
276
|
+
);
|
|
277
|
+
|
|
278
|
+
expect(io.outText()).toContain('- capability_sync');
|
|
279
|
+
expect(io.outText()).toContain('src/nested/extra.ts');
|
|
280
|
+
expect(result).toEqual({ kind: 'ok' });
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
it('仓库级漂移(与本次变更无关)→ 提示块露出,不判违规', async () => {
|
|
284
|
+
const dir = capRepo(['src/existing.ts']);
|
|
285
|
+
passTraces(dir);
|
|
286
|
+
|
|
287
|
+
const result = await check(
|
|
288
|
+
{ preset: 'standard', staged: true, projectPath: dir, trigger: 'module_modification' },
|
|
289
|
+
io
|
|
290
|
+
);
|
|
291
|
+
|
|
292
|
+
const out = io.outText();
|
|
293
|
+
expect(out).toContain('✅ 指导原则: 1/1 通过');
|
|
294
|
+
expect(out).not.toContain('指导原则警告');
|
|
295
|
+
expect(out).toContain('💡 提示: 1 条(不判违规,供参考)');
|
|
296
|
+
expect(out).toContain('- capability_sync:');
|
|
297
|
+
expect(out).toContain('src/nested/deep.ts');
|
|
298
|
+
expect(out).toContain('✅ 约束检查通过');
|
|
299
|
+
expect(result).toEqual({ kind: 'ok' });
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
it('提示与违规都写进 trace 的 evidence 字段(统计侧可诊断)', async () => {
|
|
303
|
+
const dir = capRepo(['src/existing.ts']);
|
|
304
|
+
passTraces(dir);
|
|
305
|
+
|
|
306
|
+
await check(
|
|
307
|
+
{ preset: 'standard', staged: true, projectPath: dir, trigger: 'module_modification' },
|
|
308
|
+
io
|
|
309
|
+
);
|
|
310
|
+
|
|
311
|
+
const record = traceOf('capability_sync');
|
|
312
|
+
expect(record?.result).toBe('pass');
|
|
313
|
+
expect(record?.evidence?.join('\n')).toContain('src/nested/deep.ts');
|
|
314
|
+
});
|
|
315
|
+
});
|
|
316
|
+
|
|
243
317
|
describe('git 证据单一来源(#87 验收)', () => {
|
|
244
318
|
it('两个 checker 请求同一份 staged diff → 一次 run 内只 spawn 一次', async () => {
|
|
245
319
|
// 新增(非删除)测试行:no_test_simplification 请求 stagedDiff 后放行,
|
|
@@ -7,9 +7,11 @@
|
|
|
7
7
|
*
|
|
8
8
|
* 本文件是这条约定的机器可检面,三道闸:
|
|
9
9
|
* 1. CLI 入口层:cwd 只能以 `xxx || process.cwd()` 的兜底形状出现,例外逐个点名并记理由
|
|
10
|
-
* 2.
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
* 2. 下游层(= src 减 cli,含 core / gates / context / monitoring / hooks 等):cwd 站点**逐行**冻结
|
|
11
|
+
* (harness#98,不再只冻文件键集——豁免文件内新增站点/行变形同样失败),
|
|
12
|
+
* 要么把根传下去,要么在此记下豁免理由
|
|
13
|
+
* 3. 相对路径默认值同理冻结(harness#98 收紧:对象字面量 `xxxPath: '相对'` 之外,
|
|
14
|
+
* 参数默认值形 `xxxPath = '相对'`(含类型标注)与模板字面量同罪;扫描域 = 整个下游层)
|
|
13
15
|
*
|
|
14
16
|
* 冻结集合还要求**每条豁免仍然成立**(站点消失却不删条目 → 同样失败),豁免不会烂成化石。
|
|
15
17
|
* 「根参数已传入却又取 cwd」这类静态看不出的漂移,由 project-path-anchoring.test.ts 的真实 IO 行为用例守。
|
|
@@ -83,28 +85,86 @@ const CLI_CWD_EXEMPTIONS: Record<string, { lines: string[]; reason: string }> =
|
|
|
83
85
|
* 下游豁免的共同理由:这些是**库层公开 API 的可选根参数默认值**(调用方没给根时的缺省),
|
|
84
86
|
* 不是「根已经传进来了,取 IO 时又绕回 cwd」。core 不能 import cli,兜底只能留在各自入口,
|
|
85
87
|
* 故冻结而非删除;新增站点必须在此逐个点名并单独说明。
|
|
88
|
+
*
|
|
89
|
+
* harness#98:冻结粒度从文件键集收紧到**行内容**——已豁免文件内悄悄新增 cwd 站点
|
|
90
|
+
* (不动键集)曾是绕过通道,现在新增行/行变形都会顶失败。
|
|
86
91
|
*/
|
|
87
|
-
const DOWNSTREAM_CWD_EXEMPTIONS: Record<string, string> = {
|
|
88
|
-
'src/context/session-manager.ts':
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
'src/core/constraints/
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
92
|
+
const DOWNSTREAM_CWD_EXEMPTIONS: Record<string, { lines: string[]; reason: string }> = {
|
|
93
|
+
'src/context/session-manager.ts': {
|
|
94
|
+
lines: ['this.basePath = basePath || process.cwd();'],
|
|
95
|
+
reason: '构造参数 basePath 缺省(会话存储根,CLI 已显式传 projectPath)',
|
|
96
|
+
},
|
|
97
|
+
'src/core/constraints/checker.ts': {
|
|
98
|
+
lines: [
|
|
99
|
+
'const projectPath = context.projectPath || process.cwd();',
|
|
100
|
+
'const run = evidence ?? createGitEvidence(context.projectPath || process.cwd());',
|
|
101
|
+
'const run = evidence ?? createGitEvidence(context.projectPath || process.cwd());',
|
|
102
|
+
],
|
|
103
|
+
reason: 'check 上下文缺 projectPath 时的根兜底(3 处同一形状)',
|
|
104
|
+
},
|
|
105
|
+
'src/core/constraints/checkers/types.ts': {
|
|
106
|
+
lines: ['const projectPath = context.projectPath || process.cwd();'],
|
|
107
|
+
reason: 'checker 环境的根兜底(context.projectPath 可选)',
|
|
108
|
+
},
|
|
109
|
+
'src/core/constraints/context-builder.ts': {
|
|
110
|
+
lines: [
|
|
111
|
+
'const projectPath = options.projectPath || process.cwd();',
|
|
112
|
+
'const projectPath = options.projectPath || process.cwd();',
|
|
113
|
+
],
|
|
114
|
+
reason: 'buildCheckEnv 的 options.projectPath 缺省兜底(2 处)',
|
|
115
|
+
},
|
|
116
|
+
'src/core/constraints/usage-report.ts': {
|
|
117
|
+
lines: ['projectRoot: string = process.cwd(),'],
|
|
118
|
+
reason: 'projectRoot 形参默认值(库层可选根)',
|
|
119
|
+
},
|
|
120
|
+
'src/core/effective-constraints.ts': {
|
|
121
|
+
lines: [
|
|
122
|
+
'projectRoot: string = process.cwd(),',
|
|
123
|
+
'projectRoot: string = process.cwd(),',
|
|
124
|
+
'export function lintEffectiveConfig(projectRoot: string = process.cwd()): EffectiveConfigLint {',
|
|
125
|
+
],
|
|
126
|
+
reason: '生效集三函数 projectRoot 形参默认值(库层可选根,JSDoc 已声明)',
|
|
127
|
+
},
|
|
128
|
+
'src/core/project-config-loader.ts': {
|
|
129
|
+
lines: ['this.projectPath = projectPath || process.cwd();'],
|
|
130
|
+
reason: 'config 加载器 projectPath 构造参数缺省兜底',
|
|
131
|
+
},
|
|
132
|
+
'src/core/spec/validator.ts': {
|
|
133
|
+
lines: ['const cwd = projectPath || process.cwd();'],
|
|
134
|
+
reason: 'validateAll 的可选 projectPath 兜底(其相对 schemaPath 默认值见闸 3 豁免)',
|
|
135
|
+
},
|
|
136
|
+
'src/gates/checker-gate.ts': {
|
|
137
|
+
lines: ['const projectPath = ctx.projectPath || process.cwd();'],
|
|
138
|
+
reason: 'GateContext.projectPath 缺省兜底(门禁适配器的根入口)',
|
|
139
|
+
},
|
|
140
|
+
'src/gates/effective-gates.ts': {
|
|
141
|
+
lines: ['export function getEffectiveGates(projectRoot: string = process.cwd()): Gate[] {'],
|
|
142
|
+
reason: 'projectRoot 形参默认值(库层可选根)',
|
|
143
|
+
},
|
|
144
|
+
'src/hooks/bootstrap.ts': {
|
|
145
|
+
lines: [
|
|
146
|
+
'const resolvedPath = projectPath || process.cwd();',
|
|
147
|
+
'const resolvedPath = projectPath || process.cwd();',
|
|
148
|
+
],
|
|
149
|
+
reason: 'bootstrapHarness 的可选 projectPath 兜底(组合根入口,2 处)',
|
|
150
|
+
},
|
|
151
|
+
'src/monitoring/context-tracker.ts': {
|
|
152
|
+
lines: ['const base = basePath || process.cwd();'],
|
|
153
|
+
reason: 'basePath 构造参数缺省兜底',
|
|
154
|
+
},
|
|
100
155
|
};
|
|
101
156
|
|
|
102
157
|
// ========================================
|
|
103
|
-
// 闸 3
|
|
158
|
+
// 闸 3:下游层的相对路径默认值
|
|
104
159
|
// ========================================
|
|
105
160
|
|
|
106
|
-
/**
|
|
107
|
-
|
|
161
|
+
/**
|
|
162
|
+
* key 必须 camelCase 以 Path 结尾,值为相对字面量——这类默认值按 cwd 解析,即 #95 的病根形状。
|
|
163
|
+
* harness#98 收紧匹配形状(原为只认对象字面量 `xxxPath: '相对'`):
|
|
164
|
+
* 参数默认值形 `xxxPath = '相对'`、带类型标注的 `xxxPath: string = '相对'`、模板字面量、
|
|
165
|
+
* 双引号形同罪(仓内 lint 不强制引号风格,三种引号都得认)。
|
|
166
|
+
*/
|
|
167
|
+
const RELATIVE_PATH_DEFAULT = /\b\w+Path\s*(?::\s*[^=,)]+?)?[:=]\s*['"`](?!\/)/;
|
|
108
168
|
|
|
109
169
|
const RELATIVE_PATH_DEFAULT_EXEMPTIONS: Record<string, string> = {
|
|
110
170
|
'src/core/spec/validator.ts':
|
|
@@ -133,11 +193,13 @@ describe('projectPath 传递约定(harness#95)', () => {
|
|
|
133
193
|
});
|
|
134
194
|
|
|
135
195
|
describe('闸 2:下游层不得二次取 cwd', () => {
|
|
136
|
-
it('下游 cwd
|
|
196
|
+
it('下游 cwd 站点 = 冻结豁免表且逐行一致(新增文件/新增行/行变形/豁免化石都失败)', () => {
|
|
137
197
|
const sites = cwdSites(downstreamFiles);
|
|
138
198
|
|
|
139
199
|
expect([...sites.keys()].sort()).toEqual(Object.keys(DOWNSTREAM_CWD_EXEMPTIONS).sort());
|
|
140
|
-
|
|
200
|
+
for (const [rel, exemption] of Object.entries(DOWNSTREAM_CWD_EXEMPTIONS)) {
|
|
201
|
+
expect({ rel, lines: sites.get(rel) }).toEqual({ rel, lines: exemption.lines });
|
|
202
|
+
}
|
|
141
203
|
});
|
|
142
204
|
|
|
143
205
|
it('PassesGate 执行侧不再取 cwd(站点 1 的收口形状:workDir 由调用方传入)', () => {
|
|
@@ -149,9 +211,9 @@ describe('projectPath 传递约定(harness#95)', () => {
|
|
|
149
211
|
});
|
|
150
212
|
|
|
151
213
|
describe('闸 3:相对路径默认值不再按 cwd 解析', () => {
|
|
152
|
-
it('
|
|
214
|
+
it('下游层(= src 减 cli)的相对字面量路径默认值 = 冻结豁免表', () => {
|
|
153
215
|
const hits = new Set<string>();
|
|
154
|
-
for (const file of
|
|
216
|
+
for (const file of downstreamFiles) {
|
|
155
217
|
if (codeLines(file).some(line => RELATIVE_PATH_DEFAULT.test(line))) hits.add(repoPath(file));
|
|
156
218
|
}
|
|
157
219
|
|
|
@@ -175,6 +175,12 @@ function runWithModuleProbe(argv: string[]): { status: number | null; implModule
|
|
|
175
175
|
}
|
|
176
176
|
}
|
|
177
177
|
|
|
178
|
+
// CI 上 dist 缺失时显式失败而非静默 skip(harness#99):bin 懒加载/路由的
|
|
179
|
+
// 端到端证据在下面的 smoke 块,CI 必须先 build 再 test,否则证据隐形。
|
|
180
|
+
if (!hasDist && process.env.CI) {
|
|
181
|
+
throw new Error('dist 未构建:bin 端到端 smoke 不得静默 skip —— CI 必须先 npm run build 再 npm test(harness#99)');
|
|
182
|
+
}
|
|
183
|
+
|
|
178
184
|
const smoke = hasDist ? describe : describe.skip;
|
|
179
185
|
|
|
180
186
|
smoke('bin/harness.js 端到端(dist 存在时)', () => {
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
import * as fs from 'fs';
|
|
11
11
|
import { captureIO, lastJsonOutput, type CapturingIO } from '../../command-contract';
|
|
12
12
|
import * as path from 'path';
|
|
13
|
-
import { updateUserModel } from '../update-user-model';
|
|
13
|
+
import { updateUserModel, resolveUserModelPaths } from '../update-user-model';
|
|
14
14
|
import { readTranscriptSessions, type MinedSession } from '../../session-mining';
|
|
15
15
|
|
|
16
16
|
const TEST_HOME = '/tmp/harness-uum-test-home';
|
|
@@ -54,6 +54,7 @@ const mockReadTranscriptSessions = readTranscriptSessions as jest.MockedFunction
|
|
|
54
54
|
>;
|
|
55
55
|
|
|
56
56
|
const STATE_FILE = path.join(TEST_HOME, '.claude', 'user-model-state.json');
|
|
57
|
+
const PROFILE_FILE = path.join(TEST_HOME, '.claude', 'projects', '-root-projects', 'memory', 'user_profile.md');
|
|
57
58
|
|
|
58
59
|
function todayStr(offsetDays = 0): string {
|
|
59
60
|
return new Date(Date.now() - offsetDays * 86_400_000).toISOString().slice(0, 10);
|
|
@@ -89,6 +90,39 @@ describe('update-user-model command', () => {
|
|
|
89
90
|
|
|
90
91
|
afterEach(() => {
|
|
91
92
|
delete process.env.CLAUDE_TRANSCRIPTS_DIR;
|
|
93
|
+
delete process.env.HARNESS_UUM_STATE_FILE;
|
|
94
|
+
delete process.env.HARNESS_UUM_PROFILE_FILE;
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// 路径解析(harness#116):默认值保持原行为,env 覆盖生效
|
|
98
|
+
test('resolveUserModelPaths 默认:homedir 下的原路径(现行为不变)', () => {
|
|
99
|
+
expect(resolveUserModelPaths({})).toEqual({
|
|
100
|
+
stateFile: STATE_FILE,
|
|
101
|
+
profileFile: PROFILE_FILE,
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test('resolveUserModelPaths env 覆盖:HARNESS_UUM_STATE_FILE / HARNESS_UUM_PROFILE_FILE', () => {
|
|
106
|
+
expect(resolveUserModelPaths({
|
|
107
|
+
HARNESS_UUM_STATE_FILE: '/custom/state.json',
|
|
108
|
+
HARNESS_UUM_PROFILE_FILE: '/custom/profile.md',
|
|
109
|
+
})).toEqual({
|
|
110
|
+
stateFile: '/custom/state.json',
|
|
111
|
+
profileFile: '/custom/profile.md',
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test('env 覆盖端到端:state 落 env 指定路径,不写默认路径', async () => {
|
|
116
|
+
const customState = path.join(TEST_HOME, 'custom', 'state.json');
|
|
117
|
+
process.env.HARNESS_UUM_STATE_FILE = customState;
|
|
118
|
+
mockReadTranscriptSessions.mockReturnValue([mkSession({ id: 'session-a' })]);
|
|
119
|
+
|
|
120
|
+
await updateUserModel({}, io);
|
|
121
|
+
|
|
122
|
+
expect(fs.existsSync(customState)).toBe(true);
|
|
123
|
+
expect(fs.existsSync(STATE_FILE)).toBe(false);
|
|
124
|
+
const state = JSON.parse(fs.readFileSync(customState, 'utf-8'));
|
|
125
|
+
expect(state.sessionsProcessed).toContain('session-a');
|
|
92
126
|
});
|
|
93
127
|
|
|
94
128
|
test('无新会话:提示 No new sessions to process', async () => {
|