@dommaker/harness 1.2.2 → 1.3.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/definitions.js +1 -1
- package/dist/cli/commands/definitions.js.map +1 -1
- package/dist/cli/commands/failure.d.ts.map +1 -1
- package/dist/cli/commands/failure.js +2 -1
- package/dist/cli/commands/failure.js.map +1 -1
- package/dist/cli/commands/knowledge.d.ts.map +1 -1
- package/dist/cli/commands/knowledge.js +24 -1
- package/dist/cli/commands/knowledge.js.map +1 -1
- package/dist/cli/commands/release.d.ts.map +1 -1
- package/dist/cli/commands/release.js +6 -20
- package/dist/cli/commands/release.js.map +1 -1
- package/dist/cli/commands/sync-docs/agents-syncer.js +26 -14
- package/dist/cli/commands/sync-docs/agents-syncer.js.map +1 -1
- package/dist/completion-checkers/index.d.ts +3 -3
- package/dist/completion-checkers/index.js +3 -3
- package/dist/completion-checkers/phase-format.d.ts +1 -1
- package/dist/completion-checkers/phase-format.js +2 -2
- package/dist/completion-checkers/phase-format.js.map +1 -1
- package/dist/completion-checkers/tdd-chain.d.ts +1 -1
- package/dist/completion-checkers/tdd-chain.js +2 -2
- package/dist/completion-checkers/tdd-chain.js.map +1 -1
- package/dist/completion-checkers/types.d.ts +5 -5
- package/dist/completion-checkers/types.d.ts.map +1 -1
- package/dist/completion-checkers/types.js +2 -2
- package/dist/failure/recorder.d.ts +1 -1
- package/dist/failure/recorder.js +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -4
- package/dist/index.js.map +1 -1
- package/dist/knowledge/index-generator.js +1 -1
- package/dist/knowledge/index-generator.js.map +1 -1
- package/dist/release/index.d.ts +3 -0
- package/dist/release/index.d.ts.map +1 -0
- package/dist/release/index.js +11 -0
- package/dist/release/index.js.map +1 -0
- package/dist/release/integrity.d.ts +65 -0
- package/dist/release/integrity.d.ts.map +1 -0
- package/dist/release/integrity.js +151 -0
- package/dist/release/integrity.js.map +1 -0
- package/dist/test-setup/mkdtemp-cleanup.d.ts +9 -0
- package/dist/test-setup/mkdtemp-cleanup.d.ts.map +1 -0
- package/dist/test-setup/mkdtemp-cleanup.js +90 -0
- package/dist/test-setup/mkdtemp-cleanup.js.map +1 -0
- package/dist/types/failure.d.ts +7 -0
- package/dist/types/failure.d.ts.map +1 -1
- package/dist/types/failure.js +8 -1
- package/dist/types/failure.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/failure-recorder.test.ts +9 -0
- package/src/__tests__/public-exports.test.ts +3 -0
- package/src/cli/commands/__tests__/knowledge.test.ts +35 -3
- package/src/cli/commands/__tests__/release.test.ts +22 -1
- package/src/cli/commands/__tests__/sync-docs-agents.test.ts +33 -10
- package/src/cli/commands/definitions.ts +1 -1
- package/src/cli/commands/failure.ts +2 -1
- package/src/cli/commands/knowledge.ts +26 -1
- package/src/cli/commands/release.ts +6 -20
- package/src/cli/commands/sync-docs/agents-syncer.ts +26 -13
- package/src/completion-checkers/CONTEXT.md +1 -1
- package/src/completion-checkers/__tests__/contract-presence.test.ts +1 -1
- package/src/completion-checkers/__tests__/phase-format.test.ts +1 -1
- package/src/completion-checkers/__tests__/tdd-chain.test.ts +2 -2
- package/src/completion-checkers/index.ts +3 -3
- package/src/completion-checkers/phase-format.ts +2 -2
- package/src/completion-checkers/tdd-chain.ts +2 -2
- package/src/completion-checkers/types.ts +5 -5
- package/src/failure/recorder.ts +1 -1
- package/src/index.ts +11 -2
- package/src/knowledge/__tests__/index-generator.test.ts +2 -2
- package/src/knowledge/index-generator.ts +1 -1
- package/src/release/CONTEXT.md +23 -0
- package/src/release/__tests__/integrity.test.ts +157 -0
- package/src/release/index.ts +13 -0
- package/src/release/integrity.ts +141 -0
- package/src/test-setup/__tests__/mkdtemp-cleanup.test.ts +45 -0
- package/src/test-setup/mkdtemp-cleanup.ts +83 -0
- package/src/types/failure.ts +8 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.patchMktempCleanup = patchMktempCleanup;
|
|
7
|
+
exports.isRegisteredTmpDir = isRegisteredTmpDir;
|
|
8
|
+
exports.sweepStaleMkdtempDirs = sweepStaleMkdtempDirs;
|
|
9
|
+
/**
|
|
10
|
+
* Jest setupFilesAfterEnv - mkdtemp 泄漏防护(2026-08-26 /tmp 3.9 万残留事故根因修复)
|
|
11
|
+
*
|
|
12
|
+
* 事故:5 个测试文件 fs.mkdtempSync 后零清理(constraints-retire / check-drift /
|
|
13
|
+
* constraints-report / agent-prompt-renderer / injection-drift),日常 jest 运行累积。
|
|
14
|
+
*
|
|
15
|
+
* 机制(studio apps/api/tests/mkdtemp-cleanup.ts 同款,jest 版):
|
|
16
|
+
* 1) patch fs.mkdtempSync--本进程创建的临时目录全部进注册表,afterAll 统一 rmSync。
|
|
17
|
+
* jest worker 被 kill 时 process 'exit' 不触发,必须用 afterAll(setupFilesAfterEnv
|
|
18
|
+
* 里注册 = 文件级钩子)。
|
|
19
|
+
* 2) import 时清扫 tmpdir 里 >24h 且符合 mkdtemp 签名的目录,兜底 worker 被 kill 的场景。
|
|
20
|
+
*
|
|
21
|
+
* 签名正则只匹配「小写字母数字连字符前缀 + 恰好 6 位大小写字母数字后缀」:
|
|
22
|
+
* chrome crashpad(org.chromium.Chromium.XXXX,含点)、tsx-0 / jest_0 等不匹配。
|
|
23
|
+
*/
|
|
24
|
+
const fs_1 = __importDefault(require("fs"));
|
|
25
|
+
const path_1 = __importDefault(require("path"));
|
|
26
|
+
const globals_1 = require("@jest/globals");
|
|
27
|
+
const MKDTEMP_SUFFIX_RE = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*-[A-Za-z0-9]{6}$/;
|
|
28
|
+
let patched = false;
|
|
29
|
+
const registeredDirs = new Set();
|
|
30
|
+
/** 幂等。setup 与测试文件重复调用安全。 */
|
|
31
|
+
function patchMktempCleanup() {
|
|
32
|
+
if (patched)
|
|
33
|
+
return;
|
|
34
|
+
patched = true;
|
|
35
|
+
const orig = fs_1.default.mkdtempSync;
|
|
36
|
+
fs_1.default.mkdtempSync = ((prefix) => {
|
|
37
|
+
const dir = orig(prefix);
|
|
38
|
+
registeredDirs.add(dir);
|
|
39
|
+
return dir;
|
|
40
|
+
});
|
|
41
|
+
(0, globals_1.afterAll)(() => {
|
|
42
|
+
for (const dir of registeredDirs) {
|
|
43
|
+
try {
|
|
44
|
+
fs_1.default.rmSync(dir, { recursive: true, force: true });
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
// 清理失败不阻断测试
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
registeredDirs.clear();
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
function isRegisteredTmpDir(dir) {
|
|
54
|
+
return registeredDirs.has(dir);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* 清扫 tmpDir 中超龄且符合 mkdtemp 签名的目录。返回删除数。
|
|
58
|
+
* 单条失败(竞态被别进程先删等)不阻断。
|
|
59
|
+
*/
|
|
60
|
+
function sweepStaleMkdtempDirs(tmpDir, ageMs) {
|
|
61
|
+
let entries;
|
|
62
|
+
try {
|
|
63
|
+
entries = fs_1.default.readdirSync(tmpDir);
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return 0;
|
|
67
|
+
}
|
|
68
|
+
const now = Date.now();
|
|
69
|
+
let removed = 0;
|
|
70
|
+
for (const name of entries) {
|
|
71
|
+
if (!MKDTEMP_SUFFIX_RE.test(name))
|
|
72
|
+
continue;
|
|
73
|
+
try {
|
|
74
|
+
const p = path_1.default.join(tmpDir, name);
|
|
75
|
+
const st = fs_1.default.statSync(p);
|
|
76
|
+
if (st.isDirectory() && now - st.mtimeMs > ageMs) {
|
|
77
|
+
fs_1.default.rmSync(p, { recursive: true, force: true });
|
|
78
|
+
removed++;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
// 单条竞态不阻断
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return removed;
|
|
86
|
+
}
|
|
87
|
+
// setupFilesAfterEnv 顶层执行:打补丁 + 清扫历史残留
|
|
88
|
+
patchMktempCleanup();
|
|
89
|
+
sweepStaleMkdtempDirs(process.env.TMPDIR || '/tmp', 24 * 60 * 60 * 1000);
|
|
90
|
+
//# sourceMappingURL=mkdtemp-cleanup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mkdtemp-cleanup.js","sourceRoot":"","sources":["../../src/test-setup/mkdtemp-cleanup.ts"],"names":[],"mappings":";;;;;AAyBA,gDAoBC;AAED,gDAEC;AAMD,sDAuBC;AA9ED;;;;;;;;;;;;;;GAcG;AACH,4CAAoB;AACpB,gDAAwB;AACxB,2CAAyC;AAEzC,MAAM,iBAAiB,GAAG,gDAAgD,CAAC;AAE3E,IAAI,OAAO,GAAG,KAAK,CAAC;AACpB,MAAM,cAAc,GAAG,IAAI,GAAG,EAAU,CAAC;AAEzC,4BAA4B;AAC5B,SAAgB,kBAAkB;IAChC,IAAI,OAAO;QAAE,OAAO;IACpB,OAAO,GAAG,IAAI,CAAC;IACf,MAAM,IAAI,GAAG,YAAE,CAAC,WAAyC,CAAC;IAC1D,YAAE,CAAC,WAAW,GAAG,CAAC,CAAC,MAAc,EAAE,EAAE;QACnC,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC;QACzB,cAAc,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACxB,OAAO,GAAG,CAAC;IACb,CAAC,CAA0B,CAAC;IAE5B,IAAA,kBAAQ,EAAC,GAAG,EAAE;QACZ,KAAK,MAAM,GAAG,IAAI,cAAc,EAAE,CAAC;YACjC,IAAI,CAAC;gBACH,YAAE,CAAC,MAAM,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;YACnD,CAAC;YAAC,MAAM,CAAC;gBACP,YAAY;YACd,CAAC;QACH,CAAC;QACD,cAAc,CAAC,KAAK,EAAE,CAAC;IACzB,CAAC,CAAC,CAAC;AACL,CAAC;AAED,SAAgB,kBAAkB,CAAC,GAAW;IAC5C,OAAO,cAAc,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;AACjC,CAAC;AAED;;;GAGG;AACH,SAAgB,qBAAqB,CAAC,MAAc,EAAE,KAAa;IACjE,IAAI,OAAiB,CAAC;IACtB,IAAI,CAAC;QACH,OAAO,GAAG,YAAE,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;IACnC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,CAAC,CAAC;IACX,CAAC;IACD,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACvB,IAAI,OAAO,GAAG,CAAC,CAAC;IAChB,KAAK,MAAM,IAAI,IAAI,OAAO,EAAE,CAAC;QAC3B,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,SAAS;QAC5C,IAAI,CAAC;YACH,MAAM,CAAC,GAAG,cAAI,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;YAClC,MAAM,EAAE,GAAG,YAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;YAC1B,IAAI,EAAE,CAAC,WAAW,EAAE,IAAI,GAAG,GAAG,EAAE,CAAC,OAAO,GAAG,KAAK,EAAE,CAAC;gBACjD,YAAE,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;gBAC/C,OAAO,EAAE,CAAC;YACZ,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,UAAU;QACZ,CAAC;IACH,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,uCAAuC;AACvC,kBAAkB,EAAE,CAAC;AACrB,qBAAqB,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,IAAI,MAAM,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC"}
|
package/dist/types/failure.d.ts
CHANGED
|
@@ -4,6 +4,13 @@
|
|
|
4
4
|
* 提供通用的错误分类和失败记录类型
|
|
5
5
|
* 不包含业务逻辑,只定义数据结构
|
|
6
6
|
*/
|
|
7
|
+
/**
|
|
8
|
+
* 默认 failure 日志路径(相对项目根目录)
|
|
9
|
+
*
|
|
10
|
+
* 读写双方必须统一引用此常量(DEFAULT_TRACE_FILE 同款先例);
|
|
11
|
+
* 自定义路径时向 FailureRecorder 传入自定 logFile
|
|
12
|
+
*/
|
|
13
|
+
export declare const DEFAULT_FAILURE_LOG_FILE = ".harness/logs/failures.log";
|
|
7
14
|
/**
|
|
8
15
|
* 错误类型枚举
|
|
9
16
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"failure.d.ts","sourceRoot":"","sources":["../../src/types/failure.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;GAEG;AACH,oBAAY,SAAS;IACnB,WAAW,gBAAgB;IAC3B,WAAW,gBAAgB;IAC3B,kBAAkB,uBAAuB;IACzC,gBAAgB,qBAAqB;IACrC,OAAO,YAAY;IACnB,aAAa,kBAAkB;IAC/B,WAAW,gBAAgB;IAC3B,UAAU,eAAe;IACzB,gBAAgB,qBAAqB;IACrC,OAAO,YAAY;CACpB;AAED;;;;;;;GAOG;AACH,oBAAY,YAAY;IACtB,EAAE,OAAO;IACT,EAAE,OAAO;IACT,EAAE,OAAO;IACT,EAAE,OAAO;CACV;AAED;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACtC,cAAc;IACd,IAAI,EAAE,SAAS,CAAC;IAChB,aAAa;IACb,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,oBAAoB;IACpB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,WAAW;IACX,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB,WAAW;IACX,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,WAAW;IACX,IAAI,EAAE,SAAS,CAAC;IAChB,WAAW;IACX,KAAK,EAAE,YAAY,CAAC;IACpB,WAAW;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU;IACV,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU;IACV,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,WAAW;IACX,IAAI,EAAE,SAAS,CAAC;IAChB,WAAW;IACX,KAAK,EAAE,YAAY,CAAC;IACpB,iBAAiB;IACjB,WAAW,CAAC,EAAE,uBAAuB,CAAC;IACtC,WAAW;IACX,aAAa,EAAE,KAAK,CAAC;CACtB;AAED;;;;GAIG;AACH,eAAO,MAAM,4BAA4B,EAAE,uBAAuB,EA2DjE,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,qBAAqB,EAAE,MAAM,CAAC,SAAS,EAAE,YAAY,CAWjE,CAAC"}
|
|
1
|
+
{"version":3,"file":"failure.d.ts","sourceRoot":"","sources":["../../src/types/failure.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;;;;GAKG;AACH,eAAO,MAAM,wBAAwB,+BAA+B,CAAC;AAErE;;GAEG;AACH,oBAAY,SAAS;IACnB,WAAW,gBAAgB;IAC3B,WAAW,gBAAgB;IAC3B,kBAAkB,uBAAuB;IACzC,gBAAgB,qBAAqB;IACrC,OAAO,YAAY;IACnB,aAAa,kBAAkB;IAC/B,WAAW,gBAAgB;IAC3B,UAAU,eAAe;IACzB,gBAAgB,qBAAqB;IACrC,OAAO,YAAY;CACpB;AAED;;;;;;;GAOG;AACH,oBAAY,YAAY;IACtB,EAAE,OAAO;IACT,EAAE,OAAO;IACT,EAAE,OAAO;IACT,EAAE,OAAO;CACV;AAED;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACtC,cAAc;IACd,IAAI,EAAE,SAAS,CAAC;IAChB,aAAa;IACb,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,oBAAoB;IACpB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,WAAW;IACX,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB,WAAW;IACX,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,WAAW;IACX,IAAI,EAAE,SAAS,CAAC;IAChB,WAAW;IACX,KAAK,EAAE,YAAY,CAAC;IACpB,WAAW;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU;IACV,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU;IACV,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,WAAW;IACX,IAAI,EAAE,SAAS,CAAC;IAChB,WAAW;IACX,KAAK,EAAE,YAAY,CAAC;IACpB,iBAAiB;IACjB,WAAW,CAAC,EAAE,uBAAuB,CAAC;IACtC,WAAW;IACX,aAAa,EAAE,KAAK,CAAC;CACtB;AAED;;;;GAIG;AACH,eAAO,MAAM,4BAA4B,EAAE,uBAAuB,EA2DjE,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,qBAAqB,EAAE,MAAM,CAAC,SAAS,EAAE,YAAY,CAWjE,CAAC"}
|
package/dist/types/failure.js
CHANGED
|
@@ -6,7 +6,14 @@
|
|
|
6
6
|
* 不包含业务逻辑,只定义数据结构
|
|
7
7
|
*/
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
-
exports.DEFAULT_LEVEL_MAPPING = exports.DEFAULT_CLASSIFICATION_RULES = exports.FailureLevel = exports.ErrorType = void 0;
|
|
9
|
+
exports.DEFAULT_LEVEL_MAPPING = exports.DEFAULT_CLASSIFICATION_RULES = exports.FailureLevel = exports.ErrorType = exports.DEFAULT_FAILURE_LOG_FILE = void 0;
|
|
10
|
+
/**
|
|
11
|
+
* 默认 failure 日志路径(相对项目根目录)
|
|
12
|
+
*
|
|
13
|
+
* 读写双方必须统一引用此常量(DEFAULT_TRACE_FILE 同款先例);
|
|
14
|
+
* 自定义路径时向 FailureRecorder 传入自定 logFile
|
|
15
|
+
*/
|
|
16
|
+
exports.DEFAULT_FAILURE_LOG_FILE = '.harness/logs/failures.log';
|
|
10
17
|
/**
|
|
11
18
|
* 错误类型枚举
|
|
12
19
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"failure.js","sourceRoot":"","sources":["../../src/types/failure.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"failure.js","sourceRoot":"","sources":["../../src/types/failure.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -333,3 +333,12 @@ describe('FailureRecorder', () => {
|
|
|
333
333
|
});
|
|
334
334
|
});
|
|
335
335
|
});
|
|
336
|
+
|
|
337
|
+
describe('DEFAULT_FAILURE_LOG_FILE', () => {
|
|
338
|
+
it('锁口径:统一到 .harness/logs/(与 DEFAULT_TRACE_FILE 同目录约定),从 barrel 公开导出', async () => {
|
|
339
|
+
const { DEFAULT_FAILURE_LOG_FILE } = await import('../types/failure');
|
|
340
|
+
const barrel = await import('../index');
|
|
341
|
+
expect(DEFAULT_FAILURE_LOG_FILE).toBe('.harness/logs/failures.log');
|
|
342
|
+
expect(barrel.DEFAULT_FAILURE_LOG_FILE).toBe(DEFAULT_FAILURE_LOG_FILE);
|
|
343
|
+
});
|
|
344
|
+
});
|
|
@@ -29,6 +29,7 @@ const EXPECTED_RUNTIME_EXPORTS = [
|
|
|
29
29
|
'DEFAULT_COMMAND_BLACKLIST',
|
|
30
30
|
'DEFAULT_COMPACTION_CONFIG',
|
|
31
31
|
'DEFAULT_DECAY_CONFIG',
|
|
32
|
+
'DEFAULT_FAILURE_LOG_FILE',
|
|
32
33
|
'DEFAULT_LEVEL_MAPPING',
|
|
33
34
|
'DEFAULT_NONCODE_GLOBS',
|
|
34
35
|
'DEFAULT_TEST_GLOBS',
|
|
@@ -103,6 +104,7 @@ const EXPECTED_RUNTIME_EXPORTS = [
|
|
|
103
104
|
'getCommandGate',
|
|
104
105
|
'getCommandRiskLevel',
|
|
105
106
|
'getConstraint',
|
|
107
|
+
'getCriticalArtifacts',
|
|
106
108
|
'getEffectiveConstraints',
|
|
107
109
|
'getEffectiveGates',
|
|
108
110
|
'getFailureLevel',
|
|
@@ -127,6 +129,7 @@ const EXPECTED_RUNTIME_EXPORTS = [
|
|
|
127
129
|
'validateSpec',
|
|
128
130
|
'verifyContractPresence',
|
|
129
131
|
'verifyPhaseFormat',
|
|
132
|
+
'verifyReleaseArtifacts',
|
|
130
133
|
'verifyTddChain',
|
|
131
134
|
];
|
|
132
135
|
|
|
@@ -68,13 +68,20 @@ const MOCK_REPORT = {
|
|
|
68
68
|
};
|
|
69
69
|
|
|
70
70
|
describe('getKnowledgeDir', () => {
|
|
71
|
+
const fs = require('fs');
|
|
71
72
|
const os = require('os');
|
|
72
73
|
const path = require('path');
|
|
73
74
|
const storeModule = require('../../../knowledge/store');
|
|
74
75
|
let storeCtorSpy: jest.SpyInstance;
|
|
76
|
+
let homedirSpy: jest.SpyInstance;
|
|
77
|
+
let errorSpy: jest.SpyInstance;
|
|
78
|
+
let tmpHome: string;
|
|
75
79
|
|
|
76
80
|
beforeEach(() => {
|
|
77
81
|
delete process.env.KNOWLEDGE_BASE_DIR;
|
|
82
|
+
tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), 'harness-kdir-'));
|
|
83
|
+
homedirSpy = jest.spyOn(os, 'homedir').mockReturnValue(tmpHome);
|
|
84
|
+
errorSpy = jest.spyOn(console, 'error').mockImplementation();
|
|
78
85
|
storeCtorSpy = jest.spyOn(storeModule, 'FileKnowledgeStore').mockImplementation(() => ({
|
|
79
86
|
list: jest.fn().mockReturnValue([]),
|
|
80
87
|
}));
|
|
@@ -82,7 +89,10 @@ describe('getKnowledgeDir', () => {
|
|
|
82
89
|
|
|
83
90
|
afterEach(() => {
|
|
84
91
|
storeCtorSpy.mockRestore();
|
|
92
|
+
homedirSpy.mockRestore();
|
|
93
|
+
errorSpy.mockRestore();
|
|
85
94
|
delete process.env.KNOWLEDGE_BASE_DIR;
|
|
95
|
+
fs.rmSync(tmpHome, { recursive: true, force: true });
|
|
86
96
|
});
|
|
87
97
|
|
|
88
98
|
it('should use KNOWLEDGE_BASE_DIR env var when set', async () => {
|
|
@@ -93,11 +103,33 @@ describe('getKnowledgeDir', () => {
|
|
|
93
103
|
}));
|
|
94
104
|
});
|
|
95
105
|
|
|
96
|
-
it('should default to ~/.
|
|
97
|
-
|
|
106
|
+
it('should default to ~/.harness/knowledge when no env var and no legacy data', async () => {
|
|
107
|
+
await knowledgeStats({ json: true });
|
|
108
|
+
expect(storeCtorSpy).toHaveBeenCalledWith(expect.objectContaining({
|
|
109
|
+
baseDir: path.join(tmpHome, '.harness', 'knowledge'),
|
|
110
|
+
}));
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('should keep legacy ~/.studio/knowledge when it still has data, with deprecation notice shown once', async () => {
|
|
114
|
+
const legacyDir = path.join(tmpHome, '.studio', 'knowledge');
|
|
115
|
+
fs.mkdirSync(legacyDir, { recursive: true });
|
|
116
|
+
fs.writeFileSync(path.join(legacyDir, 'guideline-x.md'), 'data');
|
|
117
|
+
await knowledgeStats({ json: true });
|
|
118
|
+
expect(storeCtorSpy).toHaveBeenCalledWith(expect.objectContaining({
|
|
119
|
+
baseDir: legacyDir,
|
|
120
|
+
}));
|
|
121
|
+
expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining('旧目录'));
|
|
122
|
+
// 闩锁:同进程重复解析只提示一次
|
|
123
|
+
await knowledgeStats({ json: true });
|
|
124
|
+
const notices = errorSpy.mock.calls.filter((c: any[]) => typeof c[0] === 'string' && c[0].includes('旧目录'));
|
|
125
|
+
expect(notices).toHaveLength(1);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('should ignore legacy dir when it exists but is empty', async () => {
|
|
129
|
+
fs.mkdirSync(path.join(tmpHome, '.studio', 'knowledge'), { recursive: true });
|
|
98
130
|
await knowledgeStats({ json: true });
|
|
99
131
|
expect(storeCtorSpy).toHaveBeenCalledWith(expect.objectContaining({
|
|
100
|
-
baseDir:
|
|
132
|
+
baseDir: path.join(tmpHome, '.harness', 'knowledge'),
|
|
101
133
|
}));
|
|
102
134
|
});
|
|
103
135
|
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import * as fs from 'fs';
|
|
15
|
+
import * as path from 'path';
|
|
15
16
|
import { execSync } from 'child_process';
|
|
16
17
|
import { release } from '../release';
|
|
17
18
|
|
|
@@ -53,7 +54,13 @@ jest.mock('chalk', () => {
|
|
|
53
54
|
const mockExecSync = execSync as jest.MockedFunction<typeof execSync>;
|
|
54
55
|
const mockFs = fs as jest.Mocked<typeof fs>;
|
|
55
56
|
|
|
56
|
-
const PKG_JSON = JSON.stringify({
|
|
57
|
+
const PKG_JSON = JSON.stringify({
|
|
58
|
+
name: '@dommaker/harness',
|
|
59
|
+
version: '0.18.0',
|
|
60
|
+
main: './dist/index.js',
|
|
61
|
+
bin: './bin/harness.js',
|
|
62
|
+
exports: { '.': './dist/index.js' },
|
|
63
|
+
});
|
|
57
64
|
|
|
58
65
|
describe('release command', () => {
|
|
59
66
|
let consoleSpy: jest.SpyInstance;
|
|
@@ -135,6 +142,20 @@ describe('release command', () => {
|
|
|
135
142
|
expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining('Uncommitted changes'));
|
|
136
143
|
});
|
|
137
144
|
|
|
145
|
+
test('dist 关键发布物缺失:发布前闸门 exit 1(不触 npm version)', async () => {
|
|
146
|
+
mockFs.existsSync.mockImplementation((p) => {
|
|
147
|
+
if (String(p).endsWith('package.json')) return true;
|
|
148
|
+
if (String(p).includes(`${path.sep}dist${path.sep}`)) return false;
|
|
149
|
+
return true;
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
await expect(release({ bumpType: 'patch' })).rejects.toThrow('__exit_1__');
|
|
153
|
+
expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining('dist missing critical artifacts'));
|
|
154
|
+
expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining('dist/index.js'));
|
|
155
|
+
const commands = mockExecSync.mock.calls.map(c => String(c[0]));
|
|
156
|
+
expect(commands.some(c => c.startsWith('npm version'))).toBe(false);
|
|
157
|
+
});
|
|
158
|
+
|
|
138
159
|
test('dry-run patch:计算目标版本 0.18.1,exit 0', async () => {
|
|
139
160
|
await expect(release({ bumpType: 'patch', dryRun: 'true' })).rejects.toThrow('__exit_0__');
|
|
140
161
|
expect(consoleSpy).toHaveBeenCalledWith(
|
|
@@ -242,18 +242,18 @@ describe('sync-docs --agents', () => {
|
|
|
242
242
|
fs.rmSync(testDir, { recursive: true, force: true });
|
|
243
243
|
});
|
|
244
244
|
|
|
245
|
-
describe('
|
|
246
|
-
it('.
|
|
247
|
-
const testDir = path.join(tempDir, '
|
|
245
|
+
describe('知识入口行:模块上下文正本模型', () => {
|
|
246
|
+
it('.harness/CONTEXT.md 存在时生成正本行指向新正本,替代散置指引,--check 无漂移', async () => {
|
|
247
|
+
const testDir = path.join(tempDir, 'canonical-context-model');
|
|
248
248
|
createFixture(testDir, { pnpm: true, withCapabilities: true });
|
|
249
249
|
// 夹具已带散置 src/CONTEXT.md:正本在场时整行替代,与是否残留散置文件无关
|
|
250
|
-
fs.mkdirSync(path.join(testDir, '.
|
|
251
|
-
fs.writeFileSync(path.join(testDir, '.
|
|
250
|
+
fs.mkdirSync(path.join(testDir, '.harness'), { recursive: true });
|
|
251
|
+
fs.writeFileSync(path.join(testDir, '.harness', 'CONTEXT.md'), '# 模块上下文\n\n## src\n\n职责:夹具\n');
|
|
252
252
|
|
|
253
253
|
await syncDocs({ projectPath: testDir, agents: true });
|
|
254
254
|
|
|
255
255
|
const content = fs.readFileSync(path.join(testDir, 'AGENTS.md'), 'utf-8');
|
|
256
|
-
expect(content).toContain('模块上下文正本:`.
|
|
256
|
+
expect(content).toContain('模块上下文正本:`.harness/CONTEXT.md`(模块锚点组织),改动代码时同步更新');
|
|
257
257
|
expect(content).not.toContain('各源码目录的 `CONTEXT.md`');
|
|
258
258
|
expect(content).not.toContain('缺失目录可由 `harness sync-docs` 生成模板');
|
|
259
259
|
// 知识库行不受影响
|
|
@@ -267,7 +267,30 @@ describe('sync-docs --agents', () => {
|
|
|
267
267
|
fs.rmSync(testDir, { recursive: true, force: true });
|
|
268
268
|
});
|
|
269
269
|
|
|
270
|
-
it('.studio/CONTEXT.md
|
|
270
|
+
it('旧正本 .studio/CONTEXT.md 仍在场时兼容沿用(免迁移),两者同在以 .harness/ 为准', async () => {
|
|
271
|
+
const legacyDir = path.join(tempDir, 'legacy-context-model');
|
|
272
|
+
createFixture(legacyDir, { pnpm: true, withCapabilities: true });
|
|
273
|
+
fs.mkdirSync(path.join(legacyDir, '.studio'), { recursive: true });
|
|
274
|
+
fs.writeFileSync(path.join(legacyDir, '.studio', 'CONTEXT.md'), '# 模块上下文\n\n## src\n\n职责:夹具\n');
|
|
275
|
+
await syncDocs({ projectPath: legacyDir, agents: true });
|
|
276
|
+
expect(fs.readFileSync(path.join(legacyDir, 'AGENTS.md'), 'utf-8'))
|
|
277
|
+
.toContain('模块上下文正本:`.studio/CONTEXT.md`(模块锚点组织),改动代码时同步更新');
|
|
278
|
+
fs.rmSync(legacyDir, { recursive: true, force: true });
|
|
279
|
+
|
|
280
|
+
const bothDir = path.join(tempDir, 'both-context-model');
|
|
281
|
+
createFixture(bothDir, { pnpm: true, withCapabilities: true });
|
|
282
|
+
fs.mkdirSync(path.join(bothDir, '.studio'), { recursive: true });
|
|
283
|
+
fs.writeFileSync(path.join(bothDir, '.studio', 'CONTEXT.md'), '# 旧\n');
|
|
284
|
+
fs.mkdirSync(path.join(bothDir, '.harness'), { recursive: true });
|
|
285
|
+
fs.writeFileSync(path.join(bothDir, '.harness', 'CONTEXT.md'), '# 新\n');
|
|
286
|
+
await syncDocs({ projectPath: bothDir, agents: true });
|
|
287
|
+
const content = fs.readFileSync(path.join(bothDir, 'AGENTS.md'), 'utf-8');
|
|
288
|
+
expect(content).toContain('模块上下文正本:`.harness/CONTEXT.md`');
|
|
289
|
+
expect(content).not.toContain('.studio/CONTEXT.md');
|
|
290
|
+
fs.rmSync(bothDir, { recursive: true, force: true });
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
it('正本不存在时保持散置模型行,--check 无漂移', async () => {
|
|
271
294
|
const testDir = path.join(tempDir, 'scattered-context-model');
|
|
272
295
|
createFixture(testDir, { pnpm: true, withCapabilities: true });
|
|
273
296
|
|
|
@@ -282,10 +305,10 @@ describe('sync-docs --agents', () => {
|
|
|
282
305
|
fs.rmSync(testDir, { recursive: true, force: true });
|
|
283
306
|
});
|
|
284
307
|
|
|
285
|
-
it('
|
|
286
|
-
const testDir = path.join(tempDir, '
|
|
308
|
+
it('正本路径是目录而非文件时不视为正本', async () => {
|
|
309
|
+
const testDir = path.join(tempDir, 'context-doc-dir');
|
|
287
310
|
createFixture(testDir, { pnpm: true, withCapabilities: true });
|
|
288
|
-
fs.mkdirSync(path.join(testDir, '.
|
|
311
|
+
fs.mkdirSync(path.join(testDir, '.harness', 'CONTEXT.md'), { recursive: true });
|
|
289
312
|
|
|
290
313
|
await syncDocs({ projectPath: testDir, agents: true });
|
|
291
314
|
|
|
@@ -251,7 +251,7 @@ export const COMMAND_DEFINITIONS: CommandDefinition[] = [
|
|
|
251
251
|
{ flags: '--title <title>', description: '知识标题(用于 upsert)' },
|
|
252
252
|
{ flags: '--content <content>', description: '知识内容 Markdown(用于 upsert)' },
|
|
253
253
|
{ flags: '--file <path>', description: '从文件读取内容(用于 upsert)' },
|
|
254
|
-
{ flags: '--source <source>', description: '知识来源 (
|
|
254
|
+
{ flags: '--source <source>', description: '知识来源 (cli/design)', defaultValue: 'cli' },
|
|
255
255
|
{ flags: '--fix', description: '自动修复(用于 audit)', defaultValue: false },
|
|
256
256
|
{ flags: '--dry-run', description: '只输出报告不修改(用于 audit)', defaultValue: false },
|
|
257
257
|
{ flags: '--threshold <n>', description: '短内容阈值(字符数,用于 audit)', defaultValue: '50' },
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import chalk from 'chalk';
|
|
8
8
|
import * as path from 'path';
|
|
9
9
|
import { FailureRecorder } from '../../failure/recorder';
|
|
10
|
+
import { DEFAULT_FAILURE_LOG_FILE } from '../../types/failure';
|
|
10
11
|
|
|
11
12
|
export interface FailureOptions {
|
|
12
13
|
/** 项目路径 */
|
|
@@ -21,7 +22,7 @@ export interface FailureOptions {
|
|
|
21
22
|
function getRecorder(projectPath?: string): FailureRecorder {
|
|
22
23
|
const base = projectPath || process.cwd();
|
|
23
24
|
return new FailureRecorder({
|
|
24
|
-
logFile: path.join(base,
|
|
25
|
+
logFile: path.join(base, DEFAULT_FAILURE_LOG_FILE),
|
|
25
26
|
});
|
|
26
27
|
}
|
|
27
28
|
|
|
@@ -499,10 +499,35 @@ export async function knowledgeHealth(options: KnowledgeOptions & { dir?: string
|
|
|
499
499
|
}
|
|
500
500
|
}
|
|
501
501
|
|
|
502
|
+
/** 缺省知识库数据根(相对用户 home) */
|
|
503
|
+
const KNOWLEDGE_DATA_DIR = path.join('.harness', 'knowledge');
|
|
504
|
+
/** 旧缺省数据根(曾寄居 studio home),仍有数据时兼容沿用,免迁移 */
|
|
505
|
+
const LEGACY_KNOWLEDGE_DATA_DIR = path.join('.studio', 'knowledge');
|
|
506
|
+
|
|
507
|
+
let legacyKnowledgeDirNotified = false;
|
|
508
|
+
|
|
509
|
+
function hasKnowledgeData(dir: string): boolean {
|
|
510
|
+
try {
|
|
511
|
+
return fs.existsSync(dir) && fs.readdirSync(dir).length > 0;
|
|
512
|
+
} catch {
|
|
513
|
+
return false;
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
|
|
502
517
|
function getKnowledgeDir(projectPath?: string): string {
|
|
503
518
|
if (projectPath) return `${projectPath}/.harness/knowledge`;
|
|
504
519
|
if (process.env.KNOWLEDGE_BASE_DIR) return process.env.KNOWLEDGE_BASE_DIR;
|
|
505
|
-
|
|
520
|
+
const defaultDir = path.join(os.homedir(), KNOWLEDGE_DATA_DIR);
|
|
521
|
+
const legacyDir = path.join(os.homedir(), LEGACY_KNOWLEDGE_DATA_DIR);
|
|
522
|
+
if (hasKnowledgeData(legacyDir)) {
|
|
523
|
+
if (!legacyKnowledgeDirNotified) {
|
|
524
|
+
legacyKnowledgeDirNotified = true;
|
|
525
|
+
// stderr:不污染 --json 的 stdout 输出
|
|
526
|
+
console.error(chalk.yellow(`⚠ 缺省知识库根已改为 ${defaultDir};旧目录 ${legacyDir} 仍有数据,本次沿用(免迁移,可用 KNOWLEDGE_BASE_DIR 覆盖)`));
|
|
527
|
+
}
|
|
528
|
+
return legacyDir;
|
|
529
|
+
}
|
|
530
|
+
return defaultDir;
|
|
506
531
|
}
|
|
507
532
|
|
|
508
533
|
/**
|
|
@@ -21,6 +21,7 @@ import { execSync } from 'child_process';
|
|
|
21
21
|
import * as fs from 'fs';
|
|
22
22
|
import * as path from 'path';
|
|
23
23
|
import chalk from 'chalk';
|
|
24
|
+
import { verifyReleaseArtifacts } from '../../release';
|
|
24
25
|
|
|
25
26
|
export interface ReleaseOptions {
|
|
26
27
|
bumpType?: 'patch' | 'minor' | 'major';
|
|
@@ -92,28 +93,13 @@ export async function release(options: ReleaseOptions): Promise<void> {
|
|
|
92
93
|
}
|
|
93
94
|
console.log(chalk.green('✅ tsc: built'));
|
|
94
95
|
|
|
95
|
-
// ── 4. Verify dist
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
'dist/knowledge/store.js',
|
|
100
|
-
'dist/knowledge/ingest.js',
|
|
101
|
-
'dist/knowledge/query.js',
|
|
102
|
-
'dist/knowledge/lifecycle.js',
|
|
103
|
-
'dist/knowledge/lint.js',
|
|
104
|
-
'dist/knowledge/types.js',
|
|
105
|
-
'dist/knowledge/import.js',
|
|
106
|
-
'dist/knowledge/reference-tracker.js',
|
|
107
|
-
'dist/knowledge/lifecycle-hooks.js',
|
|
108
|
-
'dist/knowledge/doctor.js',
|
|
109
|
-
'dist/core/constraints/prompt-injection.js',
|
|
110
|
-
];
|
|
111
|
-
const missing = criticalFiles.filter(f => !fs.existsSync(path.join(pkgPath, f)));
|
|
112
|
-
if (missing.length > 0) {
|
|
113
|
-
console.error(chalk.red(`❌ dist missing critical files: ${missing.join(', ')}`));
|
|
96
|
+
// ── 4. Verify dist(发布物完整性自检,harness#77:清单随源码维护,不再本文件硬编码)──
|
|
97
|
+
const integrity = verifyReleaseArtifacts(pkgPath);
|
|
98
|
+
if (!integrity.ok) {
|
|
99
|
+
console.error(chalk.red(`❌ dist missing critical artifacts (${integrity.missing.length}): ${integrity.missing.join(', ')}`));
|
|
114
100
|
process.exit(1);
|
|
115
101
|
}
|
|
116
|
-
console.log(chalk.green(
|
|
102
|
+
console.log(chalk.green(`✅ dist: verified (${integrity.checked.length} critical artifacts)`));
|
|
117
103
|
|
|
118
104
|
if (dryRun) {
|
|
119
105
|
const [major, minor, patch] = oldVersion.split('.').map(Number);
|
|
@@ -60,11 +60,11 @@ export interface GovernanceInfo {
|
|
|
60
60
|
*/
|
|
61
61
|
export async function buildAgentsMd(projectPath: string, srcDirs: string[]): Promise<string> {
|
|
62
62
|
const pkg = await readPackageJsonLite(projectPath);
|
|
63
|
-
const [dirs, governance, hasKnowledge,
|
|
63
|
+
const [dirs, governance, hasKnowledge, contextDocPath, contextDocCount] = await Promise.all([
|
|
64
64
|
getTopLevelDirEntries(projectPath, srcDirs),
|
|
65
65
|
getGovernanceInfo(projectPath),
|
|
66
66
|
hasKnowledgeDir(projectPath),
|
|
67
|
-
|
|
67
|
+
findCanonicalContextDoc(projectPath),
|
|
68
68
|
countContextDocs(projectPath, srcDirs),
|
|
69
69
|
]);
|
|
70
70
|
|
|
@@ -128,10 +128,10 @@ export async function buildAgentsMd(projectPath: string, srcDirs: string[]): Pro
|
|
|
128
128
|
// 写进受 --check 漂移比对的文档会制造「必然过期」竞态(任何知识写入都使已提交文档过期)
|
|
129
129
|
lines.push('- `.harness/knowledge/`:项目知识库,用 `harness knowledge` 查询');
|
|
130
130
|
}
|
|
131
|
-
if (
|
|
132
|
-
//
|
|
133
|
-
// 散置 CONTEXT.md
|
|
134
|
-
lines.push(
|
|
131
|
+
if (contextDocPath) {
|
|
132
|
+
// 正本模型:模块上下文沉淀归并到业务仓单一正本(模块锚点组织),
|
|
133
|
+
// 散置 CONTEXT.md 指引与该模型矛盾,正本在场时整行替代
|
|
134
|
+
lines.push(`- 模块上下文正本:\`${contextDocPath}\`(模块锚点组织),改动代码时同步更新`);
|
|
135
135
|
} else {
|
|
136
136
|
lines.push(
|
|
137
137
|
contextDocCount > 0
|
|
@@ -254,17 +254,30 @@ async function hasKnowledgeDir(projectPath: string): Promise<boolean> {
|
|
|
254
254
|
}
|
|
255
255
|
}
|
|
256
256
|
|
|
257
|
+
/** 模块上下文正本候选路径(相对项目根):新正本优先,旧正本兼容沿用(免迁移) */
|
|
258
|
+
const CONTEXT_DOC_PATHS: string[][] = [
|
|
259
|
+
['.harness', 'CONTEXT.md'],
|
|
260
|
+
['.studio', 'CONTEXT.md'],
|
|
261
|
+
];
|
|
262
|
+
|
|
257
263
|
/**
|
|
258
|
-
*
|
|
264
|
+
* 定位模块上下文正本(正本模型):
|
|
259
265
|
* 模块上下文沉淀归并到业务仓单一正本(模块锚点组织)时,知识入口行改指正本,
|
|
260
|
-
* 不再输出散置 CONTEXT.md
|
|
266
|
+
* 不再输出散置 CONTEXT.md 指引。正本路径缺省 `.harness/CONTEXT.md`;
|
|
267
|
+
* 旧正本 `.studio/CONTEXT.md` 在场时兼容沿用,两者同时在场上以 `.harness/` 为准。
|
|
268
|
+
* 返回相对 projectPath 的正本路径(posix 分隔符),无正本返回 null。只判存在性,不解析内容。
|
|
261
269
|
*/
|
|
262
|
-
async function
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
270
|
+
async function findCanonicalContextDoc(projectPath: string): Promise<string | null> {
|
|
271
|
+
for (const segments of CONTEXT_DOC_PATHS) {
|
|
272
|
+
try {
|
|
273
|
+
if ((await fs.stat(path.join(projectPath, ...segments))).isFile()) {
|
|
274
|
+
return segments.join('/');
|
|
275
|
+
}
|
|
276
|
+
} catch {
|
|
277
|
+
// 该候选不存在,看下一个
|
|
278
|
+
}
|
|
267
279
|
}
|
|
280
|
+
return null;
|
|
268
281
|
}
|
|
269
282
|
|
|
270
283
|
/** 统计源码根内的 CONTEXT.md 数量(含源码根本身,跨根去重) */
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# completion-checkers/
|
|
2
2
|
|
|
3
3
|
## 职责
|
|
4
|
-
|
|
4
|
+
提交集收尾软观测三纯判定函数:tdd-chain 引用链 / phase-format subject 结构 / contract-presence 契约在场。纯函数,不碰文件系统与 git,commits 由调用方(completion-gates 类守卫)供给。
|
|
5
5
|
|
|
6
6
|
## 核心导出
|
|
7
7
|
- `verifyTddChain(commits, config)`(`tdd-chain.ts`)——实现 commit 须带 trailer `Tested-By: <sha>`(`TESTED_BY_RE` 写死);被引 sha 须在本提交集内、位置在前、文件清单命中 test_globs;`Tests: none`(`TESTS_NONE_RE`)→ waiver;纯测试/纯非代码 commit 天然免检
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* verifyTddChain
|
|
2
|
+
* verifyTddChain 测试(伪造引用三形态 / Tests: none waiver / 免检分类)
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { verifyTddChain } from '../tdd-chain';
|
|
@@ -34,7 +34,7 @@ describe('verifyTddChain', () => {
|
|
|
34
34
|
const commits = [commit({ sha: sha(2), body: 'Tested-By: ' + sha(9) })];
|
|
35
35
|
const result = verifyTddChain(commits);
|
|
36
36
|
expect(result.verdict).toBe('violation');
|
|
37
|
-
expect(result.commits[0].reason).toContain('
|
|
37
|
+
expect(result.commits[0].reason).toContain('不在本提交集内');
|
|
38
38
|
});
|
|
39
39
|
|
|
40
40
|
it('伪造引用:被引 commit 位置在本 commit 之后 → violation', () => {
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Completion Checkers
|
|
2
|
+
* Completion Checkers
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* 提交集收尾软观测三件套——纯判定函数库:
|
|
5
5
|
* - verifyTddChain:实现 commit 的 Tested-By 引用链验证
|
|
6
6
|
* - verifyPhaseFormat:phase(...) subject 结构验证
|
|
7
7
|
* - verifyContractPresence:通用契约在场引擎
|
|
8
8
|
*
|
|
9
9
|
* 与 ConstraintCheck 闭环注册表无关:直接 export,不注册、不碰 checkers/index.ts。
|
|
10
|
-
* 函数不碰文件系统与 git;commits 由调用方(
|
|
10
|
+
* 函数不碰文件系统与 git;commits 由调用方(completion-gates 类守卫)供给。
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
export * from './types';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* phase-format
|
|
2
|
+
* phase-format:提交 subject 的 phase 结构验证(Q4 定稿)
|
|
3
3
|
*
|
|
4
4
|
* 协议(正则写死在函数里,不进配置):
|
|
5
5
|
* - 全部非 merge commit 的 subject 命中 `^phase\([a-z0-9-]+\):\s+\S`
|
|
@@ -32,7 +32,7 @@ export function verifyPhaseFormat(
|
|
|
32
32
|
|
|
33
33
|
const verdicts: CommitVerdict[] = commits.map((commit) => {
|
|
34
34
|
if (isMergeCommit(commit)) {
|
|
35
|
-
return { sha: commit.sha, verdict: 'violation', reason: '
|
|
35
|
+
return { sha: commit.sha, verdict: 'violation', reason: '提交集内出现 merge commit' };
|
|
36
36
|
}
|
|
37
37
|
if (!PHASE_SUBJECT_RE.test(commit.subject)) {
|
|
38
38
|
return { sha: commit.sha, verdict: 'violation', reason: `subject 不合规:${commit.subject}` };
|