@dommaker/harness 1.10.0 → 1.12.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 +31 -0
- package/dist/cli/commands/constraints-pack-proposal.d.ts +48 -0
- package/dist/cli/commands/constraints-pack-proposal.d.ts.map +1 -0
- package/dist/cli/commands/constraints-pack-proposal.js +169 -0
- package/dist/cli/commands/constraints-pack-proposal.js.map +1 -0
- package/dist/cli/commands/constraints-reactivate.d.ts +55 -0
- package/dist/cli/commands/constraints-reactivate.d.ts.map +1 -0
- package/dist/cli/commands/constraints-reactivate.js +175 -0
- package/dist/cli/commands/constraints-reactivate.js.map +1 -0
- package/dist/cli/commands/constraints-report.d.ts +12 -1
- package/dist/cli/commands/constraints-report.d.ts.map +1 -1
- package/dist/cli/commands/constraints-report.js +35 -2
- package/dist/cli/commands/constraints-report.js.map +1 -1
- package/dist/cli/commands/constraints-retire.d.ts +49 -4
- package/dist/cli/commands/constraints-retire.d.ts.map +1 -1
- package/dist/cli/commands/constraints-retire.js +116 -34
- package/dist/cli/commands/constraints-retire.js.map +1 -1
- package/dist/cli/commands/definitions.d.ts.map +1 -1
- package/dist/cli/commands/definitions.js +36 -11
- package/dist/cli/commands/definitions.js.map +1 -1
- package/dist/cli/commands/knowledge-view.d.ts +6 -3
- package/dist/cli/commands/knowledge-view.d.ts.map +1 -1
- package/dist/cli/commands/knowledge-view.js +8 -26
- package/dist/cli/commands/knowledge-view.js.map +1 -1
- package/dist/cli/commands/report.d.ts.map +1 -1
- package/dist/cli/commands/report.js +9 -5
- package/dist/cli/commands/report.js.map +1 -1
- package/dist/cli/commands/scaffold-templates.d.ts +1 -1
- package/dist/cli/commands/scaffold-templates.d.ts.map +1 -1
- package/dist/cli/commands/scaffold-templates.js +0 -16
- package/dist/cli/commands/scaffold-templates.js.map +1 -1
- package/dist/cli/state-io.d.ts +8 -1
- package/dist/cli/state-io.d.ts.map +1 -1
- package/dist/cli/state-io.js +2 -1
- package/dist/cli/state-io.js.map +1 -1
- package/dist/core/app-constraints-loader.d.ts +28 -0
- package/dist/core/app-constraints-loader.d.ts.map +1 -0
- package/dist/core/app-constraints-loader.js +187 -0
- package/dist/core/app-constraints-loader.js.map +1 -0
- package/dist/core/constraints/checker.js +1 -1
- package/dist/core/constraints/checker.js.map +1 -1
- package/dist/core/constraints/checkers/index.d.ts +20 -9
- package/dist/core/constraints/checkers/index.d.ts.map +1 -1
- package/dist/core/constraints/checkers/index.js +40 -11
- package/dist/core/constraints/checkers/index.js.map +1 -1
- package/dist/core/constraints/checkers/templated/file-exists.d.ts +16 -0
- package/dist/core/constraints/checkers/templated/file-exists.d.ts.map +1 -0
- package/dist/core/constraints/checkers/templated/file-exists.js +108 -0
- package/dist/core/constraints/checkers/templated/file-exists.js.map +1 -0
- package/dist/core/constraints/checkers/templated/glob.d.ts +10 -0
- package/dist/core/constraints/checkers/templated/glob.d.ts.map +1 -0
- package/dist/core/constraints/checkers/templated/glob.js +51 -0
- package/dist/core/constraints/checkers/templated/glob.js.map +1 -0
- package/dist/core/constraints/checkers/templated/index.d.ts +12 -0
- package/dist/core/constraints/checkers/templated/index.d.ts.map +1 -0
- package/dist/core/constraints/checkers/templated/index.js +17 -0
- package/dist/core/constraints/checkers/templated/index.js.map +1 -0
- package/dist/core/constraints/checkers/templated/regex-scan.d.ts +15 -0
- package/dist/core/constraints/checkers/templated/regex-scan.d.ts.map +1 -0
- package/dist/core/constraints/checkers/templated/regex-scan.js +129 -0
- package/dist/core/constraints/checkers/templated/regex-scan.js.map +1 -0
- package/dist/core/constraints/checkers/templates-registry.d.ts +17 -0
- package/dist/core/constraints/checkers/templates-registry.d.ts.map +1 -0
- package/dist/core/constraints/checkers/templates-registry.js +23 -0
- package/dist/core/constraints/checkers/templates-registry.js.map +1 -0
- package/dist/core/constraints/checkers/types.d.ts +14 -0
- package/dist/core/constraints/checkers/types.d.ts.map +1 -1
- package/dist/core/constraints/checkers/types.js.map +1 -1
- package/dist/core/constraints/definitions/guidelines.d.ts.map +1 -1
- package/dist/core/constraints/definitions/guidelines.js +0 -14
- package/dist/core/constraints/definitions/guidelines.js.map +1 -1
- package/dist/core/constraints/definitions/iron-laws.d.ts.map +1 -1
- package/dist/core/constraints/definitions/iron-laws.js +15 -0
- package/dist/core/constraints/definitions/iron-laws.js.map +1 -1
- package/dist/core/constraints/usage-report.d.ts +59 -7
- package/dist/core/constraints/usage-report.d.ts.map +1 -1
- package/dist/core/constraints/usage-report.js +62 -13
- package/dist/core/constraints/usage-report.js.map +1 -1
- package/dist/core/project-config-loader.d.ts +9 -6
- package/dist/core/project-config-loader.d.ts.map +1 -1
- package/dist/core/project-config-loader.js +18 -8
- package/dist/core/project-config-loader.js.map +1 -1
- package/dist/gates/command.d.ts.map +1 -1
- package/dist/gates/command.js +50 -0
- package/dist/gates/command.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -2
- package/dist/index.js.map +1 -1
- package/dist/knowledge/lifecycle.d.ts +14 -0
- package/dist/knowledge/lifecycle.d.ts.map +1 -1
- package/dist/knowledge/lifecycle.js +50 -0
- package/dist/knowledge/lifecycle.js.map +1 -1
- package/dist/knowledge/lint.d.ts +17 -0
- package/dist/knowledge/lint.d.ts.map +1 -1
- package/dist/knowledge/lint.js +52 -0
- package/dist/knowledge/lint.js.map +1 -1
- package/dist/knowledge/store.d.ts +7 -0
- package/dist/knowledge/store.d.ts.map +1 -1
- package/dist/knowledge/store.js +48 -13
- package/dist/knowledge/store.js.map +1 -1
- package/dist/knowledge/types.d.ts +5 -1
- package/dist/knowledge/types.d.ts.map +1 -1
- package/dist/knowledge/types.js +5 -1
- package/dist/knowledge/types.js.map +1 -1
- package/dist/presets/standard.d.ts +1 -1
- package/dist/presets/standard.d.ts.map +1 -1
- package/dist/presets/standard.js +2 -3
- package/dist/presets/standard.js.map +1 -1
- package/dist/types/constraint.d.ts +24 -0
- package/dist/types/constraint.d.ts.map +1 -1
- package/dist/types/constraint.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/app-constraints-loader.test.ts +220 -0
- package/src/__tests__/iron-laws.test.ts +5 -5
- package/src/__tests__/public-exports.test.ts +7 -0
- package/src/__tests__/public-type-surface.test.ts +5 -0
- package/src/__tests__/usage-report-public-export.test.ts +21 -0
- package/src/cli/commands/CONTEXT.md +5 -4
- package/src/cli/commands/__tests__/check.test.ts +22 -4
- package/src/cli/commands/__tests__/constraints-pack-proposal.test.ts +142 -0
- package/src/cli/commands/__tests__/constraints-reactivate.test.ts +210 -0
- package/src/cli/commands/__tests__/constraints-report-json-flag.test.ts +101 -0
- package/src/cli/commands/__tests__/constraints-report.test.ts +175 -6
- package/src/cli/commands/__tests__/constraints-retire.test.ts +135 -10
- package/src/cli/commands/__tests__/init-ondisk.test.ts +1 -1
- package/src/cli/commands/__tests__/init.test.ts +1 -5
- package/src/cli/commands/__tests__/knowledge.test.ts +6 -16
- package/src/cli/commands/__tests__/numeric-flag-assembly.test.ts +1 -1
- package/src/cli/commands/__tests__/passes-gate-dead-options.test.ts +1 -1
- package/src/cli/commands/__tests__/registry.test.ts +1 -1
- package/src/cli/commands/__tests__/report-app-constraints.test.ts +67 -0
- package/src/cli/commands/constraints-pack-proposal.ts +172 -0
- package/src/cli/commands/constraints-reactivate.ts +197 -0
- package/src/cli/commands/constraints-report.ts +44 -2
- package/src/cli/commands/constraints-retire.ts +129 -36
- package/src/cli/commands/definitions.ts +36 -11
- package/src/cli/commands/knowledge-view.ts +9 -28
- package/src/cli/commands/report.ts +9 -5
- package/src/cli/commands/scaffold-templates.ts +0 -16
- package/src/cli/state-io.ts +8 -1
- package/src/core/CONTEXT.md +5 -4
- package/src/core/app-constraints-loader.ts +165 -0
- package/src/core/constraints/__tests__/collect-constraints.test.ts +3 -1
- package/src/core/constraints/__tests__/usage-report.test.ts +63 -0
- package/src/core/constraints/checker.ts +1 -1
- package/src/core/constraints/checkers/__tests__/templated-checkers.test.ts +180 -0
- package/src/core/constraints/checkers/__tests__/templates-registry.test.ts +81 -0
- package/src/core/constraints/checkers/index.ts +44 -10
- package/src/core/constraints/checkers/templated/file-exists.ts +75 -0
- package/src/core/constraints/checkers/templated/glob.ts +46 -0
- package/src/core/constraints/checkers/templated/index.ts +12 -0
- package/src/core/constraints/checkers/templated/regex-scan.ts +132 -0
- package/src/core/constraints/checkers/templates-registry.ts +22 -0
- package/src/core/constraints/checkers/types.ts +15 -0
- package/src/core/constraints/definitions/guidelines.ts +0 -15
- package/src/core/constraints/definitions/iron-laws.ts +16 -0
- package/src/core/constraints/usage-report.ts +129 -14
- package/src/core/project-config-loader.ts +19 -8
- package/src/gates/__tests__/command.test.ts +54 -0
- package/src/gates/command.ts +51 -0
- package/src/index.ts +20 -0
- package/src/knowledge/CONTEXT.md +7 -4
- package/src/knowledge/__tests__/lifecycle.test.ts +81 -0
- package/src/knowledge/__tests__/lint.test.ts +84 -0
- package/src/knowledge/__tests__/store.test.ts +70 -0
- package/src/knowledge/lifecycle.ts +49 -0
- package/src/knowledge/lint.ts +60 -1
- package/src/knowledge/store.ts +48 -11
- package/src/knowledge/types.ts +7 -1
- package/src/monitoring/CONTEXT.md +1 -1
- package/src/presets/__tests__/standard.test.ts +5 -4
- package/src/presets/standard.ts +2 -3
- package/src/types/constraint.ts +28 -0
- package/dist/cli/commands/posteval-plan.d.ts +0 -15
- package/dist/cli/commands/posteval-plan.d.ts.map +0 -1
- package/dist/cli/commands/posteval-plan.js +0 -86
- package/dist/cli/commands/posteval-plan.js.map +0 -1
- package/src/cli/commands/__tests__/posteval-plan.test.ts +0 -204
- package/src/cli/commands/posteval-plan.ts +0 -98
|
@@ -12,11 +12,21 @@
|
|
|
12
12
|
* retired: { at, reason, stats: { total, fail, failRate } }
|
|
13
13
|
*
|
|
14
14
|
* 每条同时写一条 KnowledgeStore 记录(consumptionMode: 'signal')。
|
|
15
|
+
baseDir 不硬编码 projectRoot 拼接,走 openKnowledgeStore 同一解析点(harness#177):
|
|
16
|
+
缺省与 `harness knowledge` 读口同根,KNOWLEDGE_BASE_DIR 覆盖对写口同步生效。
|
|
15
17
|
*
|
|
16
|
-
* ADR-0029:custom
|
|
17
|
-
*
|
|
18
|
+
* ADR-0029:custom 纯文本约束与治理注入段同步已随文本注入层关停一并退役。
|
|
19
|
+
* ADR-0032 观察名单(块 3 子项 4):零拦截命中先进观察名单(`.harness/.state.json`
|
|
20
|
+
* `constraintWatchlist` 段,StateIO 读-改-写),挂一个季度且样本足才转退役候选——
|
|
21
|
+
* 交互模式的候选清单因此不含观察期内的零拦截约束。
|
|
22
|
+
* ADR-0033:retire 处理内置 + 应用层(`.harness/constraints.yml`)check 约束;
|
|
23
|
+
* 应用层退休墓碑同样写 config.yml,constraints.yml 条文保留不删(退休=停用不是删除),
|
|
24
|
+
* 沉淀条目 tags 加 `source:app`。
|
|
18
25
|
*
|
|
19
|
-
* retire
|
|
26
|
+
* retire 不是删除——恢复走 `harness constraints reactivate <id>`(ADR-0032 决策 6.5:
|
|
27
|
+
* 复活不改历史,写 constraint-reactivated-<id> 新条目;手动删段无沉淀,不提倡)。
|
|
28
|
+
* already_retired 幂等只认 retired 墓碑(ADR-0032 决策 6.6,票 02 断点 6):
|
|
29
|
+
* 裸 enabled:false 是"禁用"不是"退休",会被退休流程接管补上墓碑与沉淀。
|
|
20
30
|
*
|
|
21
31
|
* 交互与执行分离:retireConstraint 为纯执行逻辑(同步、可测),
|
|
22
32
|
* runRetireInteractive 只做 readline 交互,IO 流可注入。
|
|
@@ -30,15 +40,19 @@ import * as yaml from 'js-yaml';
|
|
|
30
40
|
import chalk from 'chalk';
|
|
31
41
|
import { getConstraint } from '../../core/constraints/definitions';
|
|
32
42
|
import { ProjectConfigLoader } from '../../core/project-config-loader';
|
|
33
|
-
import {
|
|
43
|
+
import { loadAppConstraints } from '../../core/app-constraints-loader';
|
|
44
|
+
import type { RunTarget } from '../../core/constraints/run-env';
|
|
34
45
|
import type { KnowledgeEntry } from '../../knowledge/types';
|
|
35
46
|
import type { Constraint } from '../../types/constraint';
|
|
47
|
+
import { openKnowledgeStore } from './knowledge-view';
|
|
48
|
+
import { fileStateIO } from '../state-io';
|
|
36
49
|
import {
|
|
37
50
|
buildConstraintsUsageReport,
|
|
38
51
|
CANDIDATE_KIND_LABEL,
|
|
39
52
|
collectUsageByConstraint,
|
|
40
53
|
readProjectTraces,
|
|
41
54
|
readProjectTracesReport,
|
|
55
|
+
WATCHLIST_PERIOD_DAYS,
|
|
42
56
|
} from '../../core/constraints/usage-report';
|
|
43
57
|
|
|
44
58
|
export interface RetireExecuteOptions {
|
|
@@ -46,6 +60,8 @@ export interface RetireExecuteOptions {
|
|
|
46
60
|
reason?: string;
|
|
47
61
|
/** 注入当前时间(测试用) */
|
|
48
62
|
now?: Date;
|
|
63
|
+
/** 知识库路径解析的 io(legacy 兜底告警走 stderr 需要;缺省 processIO) */
|
|
64
|
+
io?: CommandIO;
|
|
49
65
|
}
|
|
50
66
|
|
|
51
67
|
export type RetireStatus = 'retired' | 'already_retired' | 'unknown_id';
|
|
@@ -58,6 +74,8 @@ export interface RetireResult {
|
|
|
58
74
|
stats: { total: number; fail: number; failRate: number };
|
|
59
75
|
/** KnowledgeStore 条目 id(status='retired' 时存在) */
|
|
60
76
|
knowledgeEntryId?: string;
|
|
77
|
+
/** 退役记录实际落盘的知识库根(status='retired' 时存在,harness#177) */
|
|
78
|
+
knowledgeBaseDir?: string;
|
|
61
79
|
}
|
|
62
80
|
|
|
63
81
|
export interface ConstraintsRetireOptions {
|
|
@@ -67,28 +85,48 @@ export interface ConstraintsRetireOptions {
|
|
|
67
85
|
yes?: boolean;
|
|
68
86
|
}
|
|
69
87
|
|
|
70
|
-
interface RetireTargetInfo {
|
|
88
|
+
export interface RetireTargetInfo {
|
|
71
89
|
severity: Constraint['severity'];
|
|
72
90
|
description?: string;
|
|
73
91
|
rule?: string;
|
|
74
92
|
message?: string;
|
|
93
|
+
/** 约束来源(ADR-0033):app 层退休沉淀条目 tags 加 source:app */
|
|
94
|
+
source?: Constraint['source'];
|
|
75
95
|
}
|
|
76
96
|
|
|
77
97
|
/**
|
|
78
|
-
* 查找约束定义(内置 definitions)
|
|
98
|
+
* 查找约束定义(内置 definitions + 应用层 constraints.yml,ADR-0033)
|
|
79
99
|
*
|
|
80
100
|
* loader 由调用方给(一次退役一份观察面,见 retireConstraint):本函数只读它的装载结果,
|
|
81
|
-
* 不再自造 ProjectConfigLoader
|
|
101
|
+
* 不再自造 ProjectConfigLoader。应用层查找经 target 参数(项目根路径或观察面),
|
|
102
|
+
* 不传 = 只查内置(历史行为)。
|
|
103
|
+
*
|
|
104
|
+
* 导出给 constraints-reactivate 复用(复活同样认内置 + 应用层 check 约束)。
|
|
82
105
|
*/
|
|
83
|
-
function findRetireTarget(id: string): RetireTargetInfo | undefined {
|
|
106
|
+
export function findRetireTarget(id: string, target?: RunTarget): RetireTargetInfo | undefined {
|
|
84
107
|
const builtIn = getConstraint(id);
|
|
85
|
-
if (
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
108
|
+
if (builtIn) {
|
|
109
|
+
return {
|
|
110
|
+
severity: builtIn.severity,
|
|
111
|
+
description: builtIn.description,
|
|
112
|
+
rule: builtIn.rule,
|
|
113
|
+
message: builtIn.message,
|
|
114
|
+
source: 'builtin',
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
if (target !== undefined) {
|
|
118
|
+
const app = loadAppConstraints(target).find(c => c.id === id);
|
|
119
|
+
if (app) {
|
|
120
|
+
return {
|
|
121
|
+
severity: app.severity,
|
|
122
|
+
description: app.description,
|
|
123
|
+
rule: app.rule,
|
|
124
|
+
message: app.message,
|
|
125
|
+
source: 'app',
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return undefined;
|
|
92
130
|
}
|
|
93
131
|
|
|
94
132
|
/**
|
|
@@ -126,17 +164,45 @@ function setYamlEntry(
|
|
|
126
164
|
}
|
|
127
165
|
}
|
|
128
166
|
|
|
167
|
+
/**
|
|
168
|
+
* YAML 条目的删除单点(constraints reactivate 用,与 setYamlEntry 同一读-改-写口径)
|
|
169
|
+
*
|
|
170
|
+
* 删除 config.yml `section.id` 整个 key;段/条目不存在时零写盘。返回是否实际删除。
|
|
171
|
+
*/
|
|
172
|
+
export function removeYamlEntry(filePath: string, section: string, id: string): boolean {
|
|
173
|
+
if (!fs.existsSync(filePath)) return false;
|
|
174
|
+
const raw = (yaml.load(fs.readFileSync(filePath, 'utf-8')) as Record<string, unknown>) ?? {};
|
|
175
|
+
const entries = (raw[section] ?? {}) as Record<string, unknown>;
|
|
176
|
+
if (!(id in entries)) return false;
|
|
177
|
+
delete entries[id];
|
|
178
|
+
raw[section] = entries;
|
|
179
|
+
fs.writeFileSync(filePath, yaml.dump(raw, { lineWidth: 120 }), 'utf-8');
|
|
180
|
+
return true;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* 落盘后 commit 提示(票 02 断点 4):仅 git 仓内提示,不替用户动 git。
|
|
185
|
+
* studio 审卡通道由 applier 自动 commit,本提示面向 CLI 直达/裸项目场景。
|
|
186
|
+
*/
|
|
187
|
+
export function logCommitHint(projectRoot: string, io: CommandIO): void {
|
|
188
|
+
if (!fs.existsSync(path.join(projectRoot, '.git'))) return;
|
|
189
|
+
log(io, chalk.gray(' 提示:config.yml 已改未提交——git add .harness/config.yml && git commit(studio 审卡通道会自动 commit)'));
|
|
190
|
+
}
|
|
191
|
+
|
|
129
192
|
/**
|
|
130
193
|
* 写 KnowledgeStore 退役记录(consumptionMode: 'signal')
|
|
194
|
+
*
|
|
195
|
+
* baseDir 走 openKnowledgeStore 同一解析点(harness#177):缺省与 knowledge 读口同根,
|
|
196
|
+
* 不再硬编码 projectRoot 拼接(那会写进没有任何默认读口的 <repo>/.harness/knowledge)。
|
|
131
197
|
*/
|
|
132
198
|
function saveRetireKnowledge(
|
|
133
|
-
projectRoot: string,
|
|
134
199
|
id: string,
|
|
135
200
|
target: RetireTargetInfo,
|
|
136
201
|
reason: string,
|
|
137
202
|
stats: { total: number; fail: number; failRate: number },
|
|
138
|
-
iso: string
|
|
139
|
-
|
|
203
|
+
iso: string,
|
|
204
|
+
io: CommandIO
|
|
205
|
+
): { entryId: string; baseDir: string } {
|
|
140
206
|
const entryId = `constraint-retired-${id}`;
|
|
141
207
|
const contentLines = [
|
|
142
208
|
`# 约束退役:${id}`,
|
|
@@ -171,7 +237,13 @@ function saveRetireKnowledge(
|
|
|
171
237
|
lastReferenced: iso,
|
|
172
238
|
contributors: [],
|
|
173
239
|
projects: [],
|
|
174
|
-
|
|
240
|
+
// ADR-0033:应用层退休沉淀带 source:app 标签(内置条目不加,历史形状不动)
|
|
241
|
+
tags: [
|
|
242
|
+
'constraint-retired',
|
|
243
|
+
`constraint:${id}`,
|
|
244
|
+
`severity:${target.severity}`,
|
|
245
|
+
...(target.source === 'app' ? ['source:app'] : []),
|
|
246
|
+
],
|
|
175
247
|
applicablePhases: [],
|
|
176
248
|
sourceReferences: [{ timestamp: iso }],
|
|
177
249
|
referencedBy: [],
|
|
@@ -180,9 +252,9 @@ function saveRetireKnowledge(
|
|
|
180
252
|
origin: 'human',
|
|
181
253
|
};
|
|
182
254
|
|
|
183
|
-
const store =
|
|
255
|
+
const store = openKnowledgeStore({}, io);
|
|
184
256
|
store.save(entry);
|
|
185
|
-
return entryId;
|
|
257
|
+
return { entryId, baseDir: store.getBaseDir() };
|
|
186
258
|
}
|
|
187
259
|
|
|
188
260
|
/**
|
|
@@ -203,15 +275,18 @@ export function retireConstraint(
|
|
|
203
275
|
const loader = new ProjectConfigLoader(projectRoot);
|
|
204
276
|
loader.load();
|
|
205
277
|
|
|
206
|
-
const target = findRetireTarget(id);
|
|
278
|
+
const target = findRetireTarget(id, projectRoot);
|
|
207
279
|
const emptyStats = { total: 0, fail: 0, failRate: 0 };
|
|
208
280
|
if (!target) {
|
|
209
281
|
return { id, status: 'unknown_id', isError: false, stats: emptyStats };
|
|
210
282
|
}
|
|
211
283
|
const isError = target.severity === 'error';
|
|
212
284
|
|
|
213
|
-
//
|
|
214
|
-
|
|
285
|
+
// 已退役保护:只认 retired 墓碑(ADR-0032 决策 6.6,票 02 断点 6)——
|
|
286
|
+
// 裸 enabled:false 是"禁用"不是"退休",落到下面正常退休流程:覆写墓碑 + 补写沉淀,
|
|
287
|
+
// 不让一次裸 disable 吞掉 retire 的知识沉淀。
|
|
288
|
+
const existing = loader.getConfig().constraints?.[id] as { enabled?: boolean; retired?: unknown } | undefined;
|
|
289
|
+
if (existing?.enabled === false && existing.retired) {
|
|
215
290
|
return { id, status: 'already_retired', isError, stats: emptyStats };
|
|
216
291
|
}
|
|
217
292
|
|
|
@@ -238,7 +313,14 @@ export function retireConstraint(
|
|
|
238
313
|
);
|
|
239
314
|
|
|
240
315
|
// 2. KnowledgeStore
|
|
241
|
-
const
|
|
316
|
+
const { entryId: knowledgeEntryId, baseDir: knowledgeBaseDir } = saveRetireKnowledge(
|
|
317
|
+
id,
|
|
318
|
+
target,
|
|
319
|
+
reason,
|
|
320
|
+
stats,
|
|
321
|
+
iso,
|
|
322
|
+
options.io ?? processIO
|
|
323
|
+
);
|
|
242
324
|
|
|
243
325
|
return {
|
|
244
326
|
id,
|
|
@@ -246,25 +328,27 @@ export function retireConstraint(
|
|
|
246
328
|
isError,
|
|
247
329
|
stats,
|
|
248
330
|
knowledgeEntryId,
|
|
331
|
+
knowledgeBaseDir,
|
|
249
332
|
};
|
|
250
333
|
}
|
|
251
334
|
|
|
252
335
|
/**
|
|
253
336
|
* 打印单条退役结果(含回滚语义提示)
|
|
254
337
|
*/
|
|
255
|
-
export function printRetireResult(result: RetireResult, io: CommandIO = processIO): CommandResult {
|
|
338
|
+
export function printRetireResult(result: RetireResult, io: CommandIO = processIO, projectRoot?: string): CommandResult {
|
|
256
339
|
switch (result.status) {
|
|
257
340
|
case 'unknown_id':
|
|
258
|
-
log(io, chalk.red(`❌ ${result.id}:
|
|
341
|
+
log(io, chalk.red(`❌ ${result.id}: 约束不存在(内置与应用层约束中都未找到),未做任何变更`));
|
|
259
342
|
return { kind: 'skip', reason: `${result.id}: 约束不存在,未做任何变更` };
|
|
260
343
|
case 'already_retired':
|
|
261
|
-
log(io, chalk.yellow(`⚠️ ${result.id}: 已处于退役状态(config.yml
|
|
344
|
+
log(io, chalk.yellow(`⚠️ ${result.id}: 已处于退役状态(config.yml 有 retired 墓碑),跳过`));
|
|
262
345
|
return { kind: 'skip', reason: `${result.id}: 已处于退役状态,跳过` };
|
|
263
346
|
case 'retired': {
|
|
264
347
|
log(io, chalk.green(`✅ ${result.id}: 已退役`));
|
|
265
348
|
log(io, ` 历史统计: total=${result.stats.total} fail=${result.stats.fail} fail率=${Math.round(result.stats.failRate * 100)}%`);
|
|
266
|
-
log(io, ` 知识沉淀: ${result.knowledgeEntryId}
|
|
267
|
-
log(io, chalk.gray(` retire
|
|
349
|
+
log(io, ` 知识沉淀: ${result.knowledgeEntryId}(${result.knowledgeBaseDir})`);
|
|
350
|
+
log(io, chalk.gray(` retire 不是删除——恢复方法:harness constraints reactivate ${result.id}(会写复活沉淀;手动删 config.yml 段则无沉淀)`));
|
|
351
|
+
if (projectRoot) logCommitHint(projectRoot, io);
|
|
268
352
|
break;
|
|
269
353
|
}
|
|
270
354
|
}
|
|
@@ -297,7 +381,13 @@ export async function runRetireInteractive(
|
|
|
297
381
|
projectRoot: string,
|
|
298
382
|
io: RetireIO = { input: process.stdin, output: process.stdout }
|
|
299
383
|
): Promise<CommandResult> {
|
|
300
|
-
|
|
384
|
+
// 观察名单中间态(ADR-0032,块 3 子项 4):名单内约束不进候选;新列入经 StateIO 写回
|
|
385
|
+
const stateIO = fileStateIO(projectRoot);
|
|
386
|
+
const state = stateIO.read();
|
|
387
|
+
const report = buildConstraintsUsageReport(projectRoot, {}, { watchlist: state.constraintWatchlist });
|
|
388
|
+
if (Object.keys(report.nextWatchlist).length > Object.keys(state.constraintWatchlist ?? {}).length) {
|
|
389
|
+
stateIO.write({ ...state, constraintWatchlist: report.nextWatchlist });
|
|
390
|
+
}
|
|
301
391
|
// 候选诊断是退役决策的依据:数据不完整必须先说(harness#100,与 report 同一降级维度);
|
|
302
392
|
// 告知行走 stderr,与 status / failure list / 直达分支同一去向(交互正文仍走 console.log)
|
|
303
393
|
if (report.skippedLines > 0) {
|
|
@@ -323,6 +413,9 @@ export async function runRetireInteractive(
|
|
|
323
413
|
report.candidates.forEach((c, i) => {
|
|
324
414
|
console.log(` ${i + 1}. [${CANDIDATE_KIND_LABEL[c.kind]}] ${c.id} — ${c.reason}`);
|
|
325
415
|
});
|
|
416
|
+
if (report.watchlist.length > 0) {
|
|
417
|
+
console.log(chalk.gray(` (另有 ${report.watchlist.length} 条零拦截约束在观察名单中,满 ${WATCHLIST_PERIOD_DAYS} 天且样本足才转候选——见 harness constraints report)`));
|
|
418
|
+
}
|
|
326
419
|
console.log();
|
|
327
420
|
const answer = await ask('输入编号(逗号分隔多选)或约束 id,留空取消: ');
|
|
328
421
|
if (!answer) {
|
|
@@ -344,7 +437,7 @@ export async function runRetireInteractive(
|
|
|
344
437
|
// 逐条收集 reason + error 级二次确认
|
|
345
438
|
const plan: { id: string; reason: string }[] = [];
|
|
346
439
|
for (const id of selectedIds) {
|
|
347
|
-
const target = findRetireTarget(id);
|
|
440
|
+
const target = findRetireTarget(id, projectRoot);
|
|
348
441
|
if (!target) {
|
|
349
442
|
console.log(chalk.red(`❌ ${id}: 约束不存在,跳过`));
|
|
350
443
|
continue;
|
|
@@ -380,8 +473,8 @@ export async function runRetireInteractive(
|
|
|
380
473
|
|
|
381
474
|
console.log();
|
|
382
475
|
for (const p of plan) {
|
|
383
|
-
const result = retireConstraint(projectRoot, p.id, { reason: p.reason });
|
|
384
|
-
printRetireResult(result, out);
|
|
476
|
+
const result = retireConstraint(projectRoot, p.id, { reason: p.reason, io: out });
|
|
477
|
+
printRetireResult(result, out, projectRoot);
|
|
385
478
|
}
|
|
386
479
|
} finally {
|
|
387
480
|
close();
|
|
@@ -421,11 +514,11 @@ export async function constraintsRetire(
|
|
|
421
514
|
logError(io, chalk.yellow(`⚠️ trace 文件有 ${skippedLines} 行损坏已跳过,落盘的退役统计只基于其余合法记录`));
|
|
422
515
|
}
|
|
423
516
|
|
|
424
|
-
const result = retireConstraint(projectRoot, id, { reason: options.reason });
|
|
517
|
+
const result = retireConstraint(projectRoot, id, { reason: options.reason, io });
|
|
425
518
|
if (result.status === 'retired' && result.isError) {
|
|
426
519
|
log(io, chalk.yellow(`⚠️ ${id} 是一条 error 级约束,已通过 --yes 直达退役(交互模式会要求二次确认)`));
|
|
427
520
|
}
|
|
428
|
-
return printRetireResult(result, io);
|
|
521
|
+
return printRetireResult(result, io, projectRoot);
|
|
429
522
|
}
|
|
430
523
|
|
|
431
524
|
return runRetireInteractive(projectRoot);
|
|
@@ -295,14 +295,6 @@ export const COMMAND_DEFINITIONS: CommandDefinition[] = [
|
|
|
295
295
|
clear: { impl: { module: 'failure', export: 'failureClear' } },
|
|
296
296
|
},
|
|
297
297
|
},
|
|
298
|
-
{
|
|
299
|
-
command: 'posteval-plan',
|
|
300
|
-
argument: '<planPath>',
|
|
301
|
-
description: '验证 plan 文件的 checklist items 是否都有对应的 staged diff',
|
|
302
|
-
options: [],
|
|
303
|
-
action: { module: 'posteval-plan', export: 'postevalPlan' },
|
|
304
|
-
mapActionArgs: (positionals) => [{ planPath: positionals[0] }],
|
|
305
|
-
},
|
|
306
298
|
{
|
|
307
299
|
command: 'release',
|
|
308
300
|
description: 'npm 发布流水线:tsc → dist 验证 → npm version → git push → npm publish → gh release。不依赖 Studio API。',
|
|
@@ -327,7 +319,10 @@ export const COMMAND_DEFINITIONS: CommandDefinition[] = [
|
|
|
327
319
|
options: [
|
|
328
320
|
{ flags: '-p, --project-path <path>', description: '项目路径' },
|
|
329
321
|
{ flags: '--export [file]', description: '导出脱敏 markdown 摘要(缺省 .harness/reports/constraints-<YYYYMMDD>.md)' },
|
|
330
|
-
|
|
322
|
+
// E1 复盘修正 M3.1:原名 --json 与父命令 constraints 同名相撞,
|
|
323
|
+
// commander 把 flag 消费在父命令上导致 JSON 分支不可达;子命令换名
|
|
324
|
+
// --json-output(父 --json 有活跃消费者,子 --json 零消费者,换名零破坏)
|
|
325
|
+
{ flags: '--json-output', description: '输出 JSON 格式', defaultValue: false },
|
|
331
326
|
{ flags: '--zero-intercept-min <n>', description: '零拦截候选的最小评估样本数', defaultValue: '50' },
|
|
332
327
|
{ flags: '--noise-fail-rate <rate>', description: '高噪候选 fail 率阈值', defaultValue: '0.8' },
|
|
333
328
|
{ flags: '--noise-min-total <n>', description: '高噪候选最小评估样本数', defaultValue: '20' },
|
|
@@ -336,7 +331,7 @@ export const COMMAND_DEFINITIONS: CommandDefinition[] = [
|
|
|
336
331
|
mapActionArgs: (_pos, options) => [{
|
|
337
332
|
projectPath: options.projectPath,
|
|
338
333
|
export: options.export,
|
|
339
|
-
json: options.
|
|
334
|
+
json: options.jsonOutput,
|
|
340
335
|
zeroInterceptMin: requireNumericFlag('--zero-intercept-min', options.zeroInterceptMin as string | undefined, 'int'),
|
|
341
336
|
noiseFailRate: requireNumericFlag('--noise-fail-rate', options.noiseFailRate as string | undefined, 'float'),
|
|
342
337
|
noiseMinTotal: requireNumericFlag('--noise-min-total', options.noiseMinTotal as string | undefined, 'int'),
|
|
@@ -345,7 +340,7 @@ export const COMMAND_DEFINITIONS: CommandDefinition[] = [
|
|
|
345
340
|
{
|
|
346
341
|
command: 'retire',
|
|
347
342
|
argument: '[id]',
|
|
348
|
-
description: '退役约束:无 id 进入交互式候选选择(人确认);带 id 直达退役需显式 --yes(写 config.yml
|
|
343
|
+
description: '退役约束:无 id 进入交互式候选选择(人确认);带 id 直达退役需显式 --yes(写 config.yml retired 墓碑 + KnowledgeStore 沉淀)',
|
|
349
344
|
options: [
|
|
350
345
|
{ flags: '-p, --project-path <path>', description: '项目路径' },
|
|
351
346
|
{ flags: '--reason <text>', description: '退役原因' },
|
|
@@ -358,6 +353,36 @@ export const COMMAND_DEFINITIONS: CommandDefinition[] = [
|
|
|
358
353
|
yes: options.yes,
|
|
359
354
|
}],
|
|
360
355
|
},
|
|
356
|
+
{
|
|
357
|
+
command: 'reactivate',
|
|
358
|
+
argument: '<id>',
|
|
359
|
+
description: '复活已退役约束:删 config.yml retired 墓碑段 + 写 constraint-reactivated-<id> 沉淀(不改历史);直达需显式 --yes',
|
|
360
|
+
options: [
|
|
361
|
+
{ flags: '-p, --project-path <path>', description: '项目路径' },
|
|
362
|
+
{ flags: '--reason <text>', description: '复活原因' },
|
|
363
|
+
{ flags: '-y, --yes', description: '显式确认直达复活(与 retire 同一道人确认闸门)', defaultValue: false },
|
|
364
|
+
],
|
|
365
|
+
action: { module: 'constraints-reactivate', export: 'constraintsReactivate' },
|
|
366
|
+
mapActionArgs: (positionals, options) => [positionals[0], {
|
|
367
|
+
projectPath: options.projectPath,
|
|
368
|
+
reason: options.reason,
|
|
369
|
+
yes: options.yes,
|
|
370
|
+
}],
|
|
371
|
+
},
|
|
372
|
+
{
|
|
373
|
+
command: 'pack-proposal',
|
|
374
|
+
argument: '<id>',
|
|
375
|
+
description: '打包升级提案材料(ADR-0033 决策 4):条文 + checker 模板与参数 + traces 使用统计 + 升级理由留白,脱敏 markdown 落 .harness/reports/proposal-<id>-<YYYYMMDD>.md(--stdout 打印不落盘)',
|
|
376
|
+
options: [
|
|
377
|
+
{ flags: '-p, --project-path <path>', description: '项目路径' },
|
|
378
|
+
{ flags: '--stdout', description: '打印到 stdout 而不落盘', defaultValue: false },
|
|
379
|
+
],
|
|
380
|
+
action: { module: 'constraints-pack-proposal', export: 'constraintsPackProposal' },
|
|
381
|
+
mapActionArgs: (positionals, options) => [positionals[0], {
|
|
382
|
+
projectPath: options.projectPath,
|
|
383
|
+
stdout: options.stdout,
|
|
384
|
+
}],
|
|
385
|
+
},
|
|
361
386
|
],
|
|
362
387
|
},
|
|
363
388
|
{
|
|
@@ -9,18 +9,18 @@
|
|
|
9
9
|
* 要么声明是人读面独有的派生量(derived,须在测试的豁免表逐条登记理由)。
|
|
10
10
|
* - **角色→样式单表 + 键→中文 label 映射**:维度 label、成熟度颜色、规则 label 的唯一权威。
|
|
11
11
|
* - **json / 人读的唯一分派与退出码**:`--json` 打 `data` 的序列化,否则渲染 display model。
|
|
12
|
-
* - **路径解析与 store 构造**:`-p` / `KNOWLEDGE_BASE_DIR` /
|
|
12
|
+
* - **路径解析与 store 构造**:`-p` / `KNOWLEDGE_BASE_DIR` / 缺省目录解析只在这一处
|
|
13
|
+
* (ADR-0034:旧 ~/.studio/knowledge 兼容沿用已退役,studio 正本由编排侧显式注入)。
|
|
13
14
|
*/
|
|
14
15
|
|
|
15
16
|
import chalk from 'chalk';
|
|
16
|
-
import * as fs from 'fs';
|
|
17
17
|
import * as os from 'os';
|
|
18
18
|
import * as path from 'path';
|
|
19
19
|
import { FileKnowledgeStore } from '../../knowledge/store';
|
|
20
20
|
import { AUDIT_RULE_LABELS } from '../../knowledge/audit-scoring';
|
|
21
21
|
import type { AuditIssue, AuditReport, AuditRuleName } from '../../knowledge/audit-scoring';
|
|
22
22
|
import type { MaturityLevel } from '../../knowledge/types';
|
|
23
|
-
import { log,
|
|
23
|
+
import { log, type CommandIO, type CommandResult } from '../command-contract';
|
|
24
24
|
|
|
25
25
|
// ── display model ─────────────────────────────────────────
|
|
26
26
|
|
|
@@ -176,20 +176,8 @@ export function emitKnowledgeView<T>(io: CommandIO, options: { json?: boolean },
|
|
|
176
176
|
|
|
177
177
|
// ── 路径解析与 store 构造 ─────────────────────────────────
|
|
178
178
|
|
|
179
|
-
/** 缺省知识库数据根(相对用户 home) */
|
|
179
|
+
/** 缺省知识库数据根(相对用户 home)。裸 CLI 项目缺省目录;studio 场景唯一正本 ~/.studio/knowledge 由编排侧经 KNOWLEDGE_BASE_DIR 显式注入(ADR-0034) */
|
|
180
180
|
const KNOWLEDGE_DATA_DIR = path.join('.harness', 'knowledge');
|
|
181
|
-
/** 旧缺省数据根(曾寄居 studio home),仍有数据时兼容沿用,免迁移 */
|
|
182
|
-
const LEGACY_KNOWLEDGE_DATA_DIR = path.join('.studio', 'knowledge');
|
|
183
|
-
|
|
184
|
-
let legacyKnowledgeDirNotified = false;
|
|
185
|
-
|
|
186
|
-
function hasKnowledgeData(dir: string): boolean {
|
|
187
|
-
try {
|
|
188
|
-
return fs.existsSync(dir) && fs.readdirSync(dir).length > 0;
|
|
189
|
-
} catch {
|
|
190
|
-
return false;
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
181
|
|
|
194
182
|
export interface KnowledgePathOptions {
|
|
195
183
|
/** 覆盖知识库目录(audit/snapshot/migrate/index/health 收) */
|
|
@@ -200,22 +188,15 @@ export interface KnowledgePathOptions {
|
|
|
200
188
|
|
|
201
189
|
/**
|
|
202
190
|
* 知识库数据根的唯一解析点。`-p` 优先,其次 KNOWLEDGE_BASE_DIR,
|
|
203
|
-
* 最后落到用户 home
|
|
191
|
+
* 最后落到用户 home 缺省目录(ADR-0034 目录收编:旧 ~/.studio/knowledge
|
|
192
|
+
* 兼容沿用逻辑已退役——双目录分叉是历史包袱,studio 正本由编排侧显式注入;
|
|
193
|
+
* io 参数随兼容提示一并失去用途,签名保留免动全部调用方)。
|
|
204
194
|
*/
|
|
205
|
-
export function resolveKnowledgeBaseDir(options: KnowledgePathOptions,
|
|
195
|
+
export function resolveKnowledgeBaseDir(options: KnowledgePathOptions, _io: CommandIO): string {
|
|
206
196
|
if (options.dir) return options.dir;
|
|
207
197
|
if (options.projectPath) return `${options.projectPath}/.harness/knowledge`;
|
|
208
198
|
if (process.env.KNOWLEDGE_BASE_DIR) return process.env.KNOWLEDGE_BASE_DIR;
|
|
209
|
-
|
|
210
|
-
const legacyDir = path.join(os.homedir(), LEGACY_KNOWLEDGE_DATA_DIR);
|
|
211
|
-
if (hasKnowledgeData(legacyDir)) {
|
|
212
|
-
if (!legacyKnowledgeDirNotified) {
|
|
213
|
-
legacyKnowledgeDirNotified = true;
|
|
214
|
-
logError(io, toneText(`⚠ 缺省知识库根已改为 ${defaultDir};旧目录 ${legacyDir} 仍有数据,本次沿用(免迁移,可用 KNOWLEDGE_BASE_DIR 覆盖)`, 'warn'));
|
|
215
|
-
}
|
|
216
|
-
return legacyDir;
|
|
217
|
-
}
|
|
218
|
-
return defaultDir;
|
|
199
|
+
return path.join(os.homedir(), KNOWLEDGE_DATA_DIR);
|
|
219
200
|
}
|
|
220
201
|
|
|
221
202
|
/** 知识库句柄的唯一构造点(路径解析全权交给 resolveKnowledgeBaseDir) */
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import chalk from 'chalk';
|
|
8
8
|
import * as fs from 'fs/promises';
|
|
9
9
|
import { ConstraintChecker } from '../../core/constraints/checker';
|
|
10
|
-
import {
|
|
10
|
+
import { getMergedConstraintsConfig } from '../../core/effective-constraints';
|
|
11
11
|
import { TraceCollector } from '../../monitoring/traces';
|
|
12
12
|
import type { ConstraintContext } from '../../types/constraint';
|
|
13
13
|
import { log, processIO, type CommandIO, type CommandResult } from '../command-contract';
|
|
@@ -50,7 +50,11 @@ export async function report(options: ReportOptions, io: CommandIO = processIO):
|
|
|
50
50
|
// #139:收集器锚根构造——不传 projectPath 时 trace 会落进调用方 cwd,B 侧读不到
|
|
51
51
|
const checker = new ConstraintChecker(new TraceCollector({ projectPath }));
|
|
52
52
|
|
|
53
|
-
|
|
53
|
+
// 生效集口径与 check.ts 同一入口(ADR-0033:应用层约束进 report 面的前提)——
|
|
54
|
+
// 此前跑的是内置全集(CONSTRAINTS),config.yml 禁用与应用层条目都不进报告
|
|
55
|
+
const merged = getMergedConstraintsConfig(projectPath);
|
|
56
|
+
const effectiveConstraints = Object.values(merged.constraints);
|
|
57
|
+
const totalConstraints = effectiveConstraints.length;
|
|
54
58
|
|
|
55
59
|
const context: ConstraintContext = {
|
|
56
60
|
operation: 'file_modification',
|
|
@@ -58,7 +62,7 @@ export async function report(options: ReportOptions, io: CommandIO = processIO):
|
|
|
58
62
|
};
|
|
59
63
|
|
|
60
64
|
// 收集模式(架构评审候选1):不抛、全量收集,error 级违规如实进报告
|
|
61
|
-
const result = await checker.collectConstraints(context);
|
|
65
|
+
const result = await checker.collectConstraints(context, merged);
|
|
62
66
|
|
|
63
67
|
const failedErrors = result.errors.filter(r => !r.satisfied);
|
|
64
68
|
const failedWarnings = result.warnings.filter(r => !r.satisfied);
|
|
@@ -74,8 +78,8 @@ export async function report(options: ReportOptions, io: CommandIO = processIO):
|
|
|
74
78
|
projectPath,
|
|
75
79
|
constraints: {
|
|
76
80
|
total: totalConstraints,
|
|
77
|
-
errors:
|
|
78
|
-
warnings:
|
|
81
|
+
errors: effectiveConstraints.filter(c => c.severity === 'error').length,
|
|
82
|
+
warnings: effectiveConstraints.filter(c => c.severity === 'warning').length,
|
|
79
83
|
passed: result.passed ? totalConstraints : totalConstraints - violations.length,
|
|
80
84
|
failed: failedErrors.length,
|
|
81
85
|
warningViolations: failedWarnings.length,
|
|
@@ -17,8 +17,6 @@ import type { CiPlatform } from '../../types/project-config';
|
|
|
17
17
|
export const PRE_COMMIT_SNIPPET = `
|
|
18
18
|
echo "🔍 Running harness checks..."
|
|
19
19
|
|
|
20
|
-
STAGED=$(git diff --cached --name-only --diff-filter=ACMR 2>/dev/null || true)
|
|
21
|
-
|
|
22
20
|
# Harness 约束检查
|
|
23
21
|
npx @dommaker/harness check --staged
|
|
24
22
|
if [ $? -ne 0 ]; then
|
|
@@ -26,20 +24,6 @@ if [ $? -ne 0 ]; then
|
|
|
26
24
|
exit 1
|
|
27
25
|
fi
|
|
28
26
|
|
|
29
|
-
# Plan coverage check (via PostEval)
|
|
30
|
-
if command -v npx > /dev/null 2>&1; then
|
|
31
|
-
PLAN_FILES=$(echo "$STAGED" | grep -E 'plans/.*\\.md$|\\.plan\\.md$' || true)
|
|
32
|
-
if [ -n "$PLAN_FILES" ]; then
|
|
33
|
-
echo "📋 Checking plan coverage..."
|
|
34
|
-
for plan in $PLAN_FILES; do
|
|
35
|
-
npx @dommaker/harness posteval-plan "$plan" || {
|
|
36
|
-
echo "🛑 Plan coverage incomplete. See above for missed items."
|
|
37
|
-
exit 1
|
|
38
|
-
}
|
|
39
|
-
done
|
|
40
|
-
fi
|
|
41
|
-
fi
|
|
42
|
-
|
|
43
27
|
echo "✅ All checks passed"
|
|
44
28
|
`;
|
|
45
29
|
|
package/src/cli/state-io.ts
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
*
|
|
4
4
|
* `.harness/.state.json` 是 harness **自身**的运行期状态(与 RunEnv 观察的项目上行
|
|
5
5
|
* 数据不是一类东西,RunEnv 保持 ADR-0023 的只读契约)。本模块是它读-改-写的唯一
|
|
6
|
-
* 入口:`check`
|
|
6
|
+
* 入口:`check` 的智能提示、`status` 与 `constraints report`/`retire` 的观察名单
|
|
7
|
+
* (块 3 子项 4)都经本接缝,命令函数以可选参数注入,
|
|
7
8
|
* 缺省 = 真实 fs 实现(照 `CommandIO` 的注入模式,但不扩 `CommandIO`——输出面与
|
|
8
9
|
* 状态面是两个概念)。
|
|
9
10
|
*
|
|
@@ -12,6 +13,7 @@
|
|
|
12
13
|
|
|
13
14
|
import * as fs from 'fs';
|
|
14
15
|
import * as path from 'path';
|
|
16
|
+
import type { ConstraintWatchlistState } from '../core/constraints/usage-report';
|
|
15
17
|
|
|
16
18
|
/**
|
|
17
19
|
* `.harness/.state.json` 的具名类型(从 check.ts 内联类型提出,ADR-0026 决策 1)。
|
|
@@ -23,6 +25,11 @@ export interface HarnessState {
|
|
|
23
25
|
shownHints?: string[];
|
|
24
26
|
/** 最近一次 `harness status` 运行时刻(ISO 串) */
|
|
25
27
|
lastStatusRun?: string;
|
|
28
|
+
/**
|
|
29
|
+
* 零拦截观察名单(ADR-0032,块 3 子项 4):constraintId → 列入时刻。
|
|
30
|
+
* `constraints report` / `constraints retire` 经本接缝读-改-写(只增不删)。
|
|
31
|
+
*/
|
|
32
|
+
constraintWatchlist?: ConstraintWatchlistState;
|
|
26
33
|
}
|
|
27
34
|
|
|
28
35
|
/** harness 自身运行期状态文件的可注入接缝(读-改-写的唯一入口) */
|