@dommaker/harness 1.2.3 → 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.
Files changed (73) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/dist/cli/commands/definitions.js +1 -1
  3. package/dist/cli/commands/definitions.js.map +1 -1
  4. package/dist/cli/commands/failure.d.ts.map +1 -1
  5. package/dist/cli/commands/failure.js +2 -1
  6. package/dist/cli/commands/failure.js.map +1 -1
  7. package/dist/cli/commands/knowledge.d.ts.map +1 -1
  8. package/dist/cli/commands/knowledge.js +24 -1
  9. package/dist/cli/commands/knowledge.js.map +1 -1
  10. package/dist/cli/commands/release.d.ts.map +1 -1
  11. package/dist/cli/commands/release.js +6 -20
  12. package/dist/cli/commands/release.js.map +1 -1
  13. package/dist/cli/commands/sync-docs/agents-syncer.js +26 -14
  14. package/dist/cli/commands/sync-docs/agents-syncer.js.map +1 -1
  15. package/dist/completion-checkers/index.d.ts +3 -3
  16. package/dist/completion-checkers/index.js +3 -3
  17. package/dist/completion-checkers/phase-format.d.ts +1 -1
  18. package/dist/completion-checkers/phase-format.js +2 -2
  19. package/dist/completion-checkers/phase-format.js.map +1 -1
  20. package/dist/completion-checkers/tdd-chain.d.ts +1 -1
  21. package/dist/completion-checkers/tdd-chain.js +2 -2
  22. package/dist/completion-checkers/tdd-chain.js.map +1 -1
  23. package/dist/completion-checkers/types.d.ts +5 -5
  24. package/dist/completion-checkers/types.d.ts.map +1 -1
  25. package/dist/completion-checkers/types.js +2 -2
  26. package/dist/failure/recorder.d.ts +1 -1
  27. package/dist/failure/recorder.js +1 -1
  28. package/dist/index.d.ts +3 -1
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js +13 -4
  31. package/dist/index.js.map +1 -1
  32. package/dist/knowledge/index-generator.js +1 -1
  33. package/dist/knowledge/index-generator.js.map +1 -1
  34. package/dist/release/index.d.ts +3 -0
  35. package/dist/release/index.d.ts.map +1 -0
  36. package/dist/release/index.js +11 -0
  37. package/dist/release/index.js.map +1 -0
  38. package/dist/release/integrity.d.ts +65 -0
  39. package/dist/release/integrity.d.ts.map +1 -0
  40. package/dist/release/integrity.js +151 -0
  41. package/dist/release/integrity.js.map +1 -0
  42. package/dist/types/failure.d.ts +7 -0
  43. package/dist/types/failure.d.ts.map +1 -1
  44. package/dist/types/failure.js +8 -1
  45. package/dist/types/failure.js.map +1 -1
  46. package/package.json +1 -1
  47. package/src/__tests__/failure-recorder.test.ts +9 -0
  48. package/src/__tests__/public-exports.test.ts +3 -0
  49. package/src/cli/commands/__tests__/knowledge.test.ts +35 -3
  50. package/src/cli/commands/__tests__/release.test.ts +22 -1
  51. package/src/cli/commands/__tests__/sync-docs-agents.test.ts +33 -10
  52. package/src/cli/commands/definitions.ts +1 -1
  53. package/src/cli/commands/failure.ts +2 -1
  54. package/src/cli/commands/knowledge.ts +26 -1
  55. package/src/cli/commands/release.ts +6 -20
  56. package/src/cli/commands/sync-docs/agents-syncer.ts +26 -13
  57. package/src/completion-checkers/CONTEXT.md +1 -1
  58. package/src/completion-checkers/__tests__/contract-presence.test.ts +1 -1
  59. package/src/completion-checkers/__tests__/phase-format.test.ts +1 -1
  60. package/src/completion-checkers/__tests__/tdd-chain.test.ts +2 -2
  61. package/src/completion-checkers/index.ts +3 -3
  62. package/src/completion-checkers/phase-format.ts +2 -2
  63. package/src/completion-checkers/tdd-chain.ts +2 -2
  64. package/src/completion-checkers/types.ts +5 -5
  65. package/src/failure/recorder.ts +1 -1
  66. package/src/index.ts +11 -2
  67. package/src/knowledge/__tests__/index-generator.test.ts +2 -2
  68. package/src/knowledge/index-generator.ts +1 -1
  69. package/src/release/CONTEXT.md +23 -0
  70. package/src/release/__tests__/integrity.test.ts +157 -0
  71. package/src/release/index.ts +13 -0
  72. package/src/release/integrity.ts +141 -0
  73. package/src/types/failure.ts +8 -0
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dommaker/harness",
3
- "version": "1.2.3",
3
+ "version": "1.3.0",
4
4
  "description": "通用工程约束框架 - 铁律系统、检查点验证、测试门控",
5
5
  "keywords": [
6
6
  "harness",
@@ -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 ~/.studio/knowledge when no env var', async () => {
97
- const expected = path.join(os.homedir(), '.studio', 'knowledge');
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: expected,
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({ name: '@dommaker/harness', version: '0.18.0' });
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('知识入口行:.studio/CONTEXT.md 正本模型(studio #188)', () => {
246
- it('.studio/CONTEXT.md 存在时生成正本行,替代散置指引(散置文件同时在场也一样),--check 无漂移', async () => {
247
- const testDir = path.join(tempDir, 'studio-context-model');
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, '.studio'), { recursive: true });
251
- fs.writeFileSync(path.join(testDir, '.studio', 'CONTEXT.md'), '# 模块上下文\n\n## src\n\n职责:夹具\n');
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('模块上下文正本:`.studio/CONTEXT.md`(模块锚点组织),改动代码时同步更新');
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 不存在时保持散置模型行,--check 无漂移', async () => {
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('.studio/CONTEXT.md 是目录而非文件时不视为正本', async () => {
286
- const testDir = path.join(tempDir, 'studio-context-dir');
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, '.studio', 'CONTEXT.md'), { recursive: true });
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: '知识来源 (analyst/cli/design)', defaultValue: 'cli' },
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, '.harness', 'failures', 'failures.log'),
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
- return path.join(os.homedir(), '.studio', 'knowledge');
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 criticalFiles = [
97
- 'dist/index.js',
98
- 'dist/knowledge/index.js',
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('✅ dist: verified'));
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, hasStudioContext, contextDocCount] = await Promise.all([
63
+ const [dirs, governance, hasKnowledge, contextDocPath, contextDocCount] = await Promise.all([
64
64
  getTopLevelDirEntries(projectPath, srcDirs),
65
65
  getGovernanceInfo(projectPath),
66
66
  hasKnowledgeDir(projectPath),
67
- hasStudioContextDoc(projectPath),
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 (hasStudioContext) {
132
- // 正本模型(studio #152/T12):模块上下文沉淀归并到业务仓 .studio/CONTEXT.md,
133
- // 散置 CONTEXT.md 指引与该裁决矛盾,正本在场时整行替代
134
- lines.push('- 模块上下文正本:`.studio/CONTEXT.md`(模块锚点组织),改动代码时同步更新');
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
- * 判断 .studio/CONTEXT.md 是否存在(正本模型标记,studio #152/T12):
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 hasStudioContextDoc(projectPath: string): Promise<boolean> {
263
- try {
264
- return (await fs.stat(path.join(projectPath, '.studio', 'CONTEXT.md'))).isFile();
265
- } catch {
266
- return false;
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
- WU 收尾软观测三纯判定函数(T7-E1,studio#160):tdd-chain 引用链 / phase-format subject 结构 / contract-presence 契约在场。纯函数,不碰文件系统与 git,commits 由调用方(studio completion-gates 第四段守卫,T7-E2)供给。
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
- * verifyContractPresence 测试(studio#160 验收:契约标记缺失 / 无表项 skip)
2
+ * verifyContractPresence 测试(契约标记缺失 / 无表项 skip)
3
3
  */
4
4
 
5
5
  import { verifyContractPresence } from '../contract-presence';
@@ -1,5 +1,5 @@
1
1
  /**
2
- * verifyPhaseFormat 测试(studio#160 验收:subject 不合规 / merge commit 违规)
2
+ * verifyPhaseFormat 测试(subject 不合规 / merge commit 违规)
3
3
  */
4
4
 
5
5
  import { verifyPhaseFormat } from '../phase-format';
@@ -1,5 +1,5 @@
1
1
  /**
2
- * verifyTddChain 测试(studio#160 验收:伪造引用三形态 / Tests: none waiver / 免检分类)
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('不在本 WU 提交集内');
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(T7-E1,studio#160)
2
+ * Completion Checkers
3
3
  *
4
- * WU 收尾软观测三件套——纯判定函数库:
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 由调用方(studio 第四段守卫,T7-E2)供给。
10
+ * 函数不碰文件系统与 git;commits 由调用方(completion-gates 类守卫)供给。
11
11
  */
12
12
 
13
13
  export * from './types';
@@ -1,5 +1,5 @@
1
1
  /**
2
- * phase-format:WU 提交 subject 的 phase 结构验证(Q4 定稿)
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: 'WU 提交集内出现 merge commit' };
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}` };
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * 协议(写死为机制本体,不进配置):
5
5
  * - 实现 commit 必须带 trailer `Tested-By: <sha>`
6
- * - 被引 sha 须 a) 在本 WU 提交集内 b) 序列位置在本 commit 之前(比位置不比时间戳)c) 其文件清单命中 test_globs
6
+ * - 被引 sha 须 a) 在本提交集内 b) 序列位置在本 commit 之前(比位置不比时间戳)c) 其文件清单命中 test_globs
7
7
  * - 豁免:trailer `Tests: none` → waiver(放行记台账);纯非代码/纯测试 commit 天然免检
8
8
  */
9
9
 
@@ -44,7 +44,7 @@ export function verifyTddChain(
44
44
  const ref = m[1].toLowerCase();
45
45
  const j = commits.findIndex((c) => c.sha.toLowerCase().startsWith(ref));
46
46
  if (j === -1) {
47
- return { sha: commit.sha, verdict: 'violation', reason: `Tested-By 引用 ${ref} 不在本 WU 提交集内` };
47
+ return { sha: commit.sha, verdict: 'violation', reason: `Tested-By 引用 ${ref} 不在本提交集内` };
48
48
  }
49
49
  if (j >= i) {
50
50
  return { sha: commit.sha, verdict: 'violation', reason: `Tested-By 引用 ${ref} 位置不在本 commit 之前` };
@@ -1,12 +1,12 @@
1
1
  /**
2
- * Completion Checkers 类型定义(T7-E1,studio#160)
2
+ * Completion Checkers 类型定义
3
3
  *
4
4
  * 三个纯判定函数共享的输入/输出/配置类型。
5
5
  * 与 ConstraintCheck 闭环注册表无关:直接 export,不注册、不碰 checkers/index.ts。
6
- * 函数不碰文件系统与 git,commits 由调用方(studio 侧 git log)供给。
6
+ * 函数不碰文件系统与 git,commits 由调用方(git log)供给。
7
7
  */
8
8
 
9
- /** WU 提交输入(有序,base..HEAD 升序) */
9
+ /** 提交集内的单个提交输入(有序,base..HEAD 升序) */
10
10
  export interface CommitInput {
11
11
  /** 完整 sha */
12
12
  sha: string;
@@ -49,7 +49,7 @@ export interface PhaseFormatResult {
49
49
 
50
50
  /** verifyContractPresence 上下文(类型→字段的判定方法映射是代码不是配置) */
51
51
  export interface ContractPresenceContext {
52
- /** review 类型契约:studio agent-loop 已解析的 metadata.reviewReport */
52
+ /** review 类型契约:调用方已解析的 metadata.reviewReport */
53
53
  reviewReport?: unknown;
54
54
  [key: string]: unknown;
55
55
  }
@@ -62,7 +62,7 @@ export interface ContractPresenceResult {
62
62
  }
63
63
 
64
64
  /**
65
- * completion_checkers 配置(对应 yml 顶层键 `completion_checkers:`,studio 侧自解)。
65
+ * completion_checkers 配置(对应 yml 顶层键 `completion_checkers:`,消费方自解)。
66
66
  * 只配开关/glob/契约类型清单;协议格式(Tested-By、Tests: none、phase 结构)写死为机制本体。
67
67
  * 缺省 = 全开 + 默认 glob。
68
68
  */
@@ -25,7 +25,7 @@ export interface FailureRecorderConfig {
25
25
  *
26
26
  * 用法:
27
27
  * ```typescript
28
- * const recorder = new FailureRecorder({ logFile: '.harness/logs/failures.log' });
28
+ * const recorder = new FailureRecorder({ logFile: DEFAULT_FAILURE_LOG_FILE });
29
29
  * await recorder.record({
30
30
  * type: ErrorType.TEST_FAILED,
31
31
  * level: FailureLevel.L1,
package/src/index.ts CHANGED
@@ -235,6 +235,7 @@ export type { ContextAverages } from './monitoring';
235
235
  export {
236
236
  ErrorType,
237
237
  FailureLevel,
238
+ DEFAULT_FAILURE_LOG_FILE,
238
239
  DEFAULT_CLASSIFICATION_RULES,
239
240
  DEFAULT_LEVEL_MAPPING,
240
241
  ErrorClassifier,
@@ -339,8 +340,8 @@ export type {
339
340
  } from './knowledge';
340
341
 
341
342
  // ========================================
342
- // Completion Checkers(T7-E1,studio#160)
343
- // WU 收尾软观测三纯判定函数:tdd-chain / phase-format / contract-presence。
343
+ // Completion Checkers
344
+ // 提交集收尾软观测三纯判定函数:tdd-chain / phase-format / contract-presence。
344
345
  // 纯函数直接 export,不进 ConstraintCheck 闭环注册表。
345
346
  // ========================================
346
347
  export {
@@ -374,6 +375,14 @@ export type {
374
375
  // ========================================
375
376
  export { getRegistryPath, getToolsDir } from './tools';
376
377
 
378
+ // ========================================
379
+ // 发布物完整性自检(#75 N4 收编:harness 自己最知道自己发了什么)
380
+ // 清单 = 包声明面(package.json main/exports/bin)推导 + 运行时 extras 随源码维护;
381
+ // studio publishPackage 的 dist 校验改调本能力,不再硬编码 harness dist 内部清单。
382
+ // ========================================
383
+ export { getCriticalArtifacts, verifyReleaseArtifacts } from './release';
384
+ export type { ArtifactIntegrityResult } from './release';
385
+
377
386
  // ========================================
378
387
  // Hooks 管线
379
388
  // ========================================
@@ -103,7 +103,7 @@ Extra text
103
103
  it('handles files without frontmatter via best-effort', () => {
104
104
  fs.writeFileSync(
105
105
  path.join(tmpDir, 'agent-network-core-concepts.md'),
106
- '# Agent Network 核心概念\n\n## WorkUnit\nBody\n'
106
+ '# 核心概念总览\n\n## 设计要点\nBody\n'
107
107
  );
108
108
 
109
109
  const gen = new KnowledgeIndexGenerator(tmpDir);
@@ -115,7 +115,7 @@ Extra text
115
115
  // agent-network-* prefix maps to architecture
116
116
  expect(fields[2]).toBe('architecture');
117
117
  // H2 terms should still be extracted
118
- expect(fields[6]).toContain('WorkUnit');
118
+ expect(fields[6]).toContain('设计要点');
119
119
  });
120
120
 
121
121
  it('excludes .archive directory', () => {
@@ -59,7 +59,7 @@ export class KnowledgeIndexGenerator {
59
59
  `# Types: ${Object.entries(byType).map(([t, n]) => `${t}:${n}`).join(' ')}`,
60
60
  `#`,
61
61
  `# Usage:`,
62
- `# grep "<keyword>" ~/.studio/knowledge/_index.md`,
62
+ `# grep "<keyword>" ~/.harness/knowledge/_index.md`,
63
63
  `# Then Read the matching file for full content.`,
64
64
  `#`,
65
65
  `# filename|id|type|title|maturity|tags|terms`,