@dommaker/harness 1.11.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 +20 -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 +31 -1
- 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/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 +58 -6
- package/dist/core/constraints/usage-report.d.ts.map +1 -1
- package/dist/core/constraints/usage-report.js +61 -12
- 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/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/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/cli/commands/CONTEXT.md +3 -2
- 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.test.ts +175 -6
- package/src/cli/commands/__tests__/constraints-retire.test.ts +135 -10
- package/src/cli/commands/__tests__/knowledge.test.ts +6 -16
- 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 +31 -1
- package/src/cli/commands/knowledge-view.ts +9 -28
- package/src/cli/commands/report.ts +9 -5
- 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 +128 -13
- 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/knowledge/CONTEXT.md +3 -1
- package/src/knowledge/__tests__/lifecycle.test.ts +81 -0
- package/src/knowledge/lifecycle.ts +49 -0
- package/src/presets/__tests__/standard.test.ts +5 -4
- package/src/presets/standard.ts +2 -3
- package/src/types/constraint.ts +28 -0
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* harness constraints reactivate —— 约束复活(ADR-0032 决策 6.5,票 02 断点 5)
|
|
3
|
+
*
|
|
4
|
+
* 复活 = 撤销退休:删 config.yml `constraints.<id>` 墓碑段(与既有"恢复方法"语义一致),
|
|
5
|
+
* 同时写一条 KnowledgeStore 新条目 `constraint-reactivated-<id>`(consumptionMode: 'signal')
|
|
6
|
+
* ——不改历史:旧 `constraint-retired-<id>` 沉淀原样保留,复活原因单独成条。
|
|
7
|
+
*
|
|
8
|
+
* 幂等口径与 retire 对偶:只认 retired 墓碑。裸 enabled:false(禁用非退休)与
|
|
9
|
+
* 从未退休的约束一律 not_retired,不落盘任何文件。
|
|
10
|
+
*
|
|
11
|
+
* 人确认闸门与 retire 同形(ADR-0001 决策 2 语义):直达必须显式 --yes。
|
|
12
|
+
* 不提供交互批量模式——复活是低频点对点操作。
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import * as path from 'path';
|
|
16
|
+
import { log, logError, processIO, type CommandIO, type CommandResult } from '../command-contract';
|
|
17
|
+
import chalk from 'chalk';
|
|
18
|
+
import { ProjectConfigLoader } from '../../core/project-config-loader';
|
|
19
|
+
import type { KnowledgeEntry } from '../../knowledge/types';
|
|
20
|
+
import { openKnowledgeStore } from './knowledge-view';
|
|
21
|
+
import { findRetireTarget, logCommitHint, removeYamlEntry, type RetireTargetInfo } from './constraints-retire';
|
|
22
|
+
|
|
23
|
+
export interface ReactivateExecuteOptions {
|
|
24
|
+
/** 复活原因(可空) */
|
|
25
|
+
reason?: string;
|
|
26
|
+
/** 注入当前时间(测试用) */
|
|
27
|
+
now?: Date;
|
|
28
|
+
/** 知识库路径解析的 io(缺省 processIO,与 retire 写口同形) */
|
|
29
|
+
io?: CommandIO;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export type ReactivateStatus = 'reactivated' | 'not_retired' | 'unknown_id';
|
|
33
|
+
|
|
34
|
+
export interface ReactivateResult {
|
|
35
|
+
id: string;
|
|
36
|
+
status: ReactivateStatus;
|
|
37
|
+
/** KnowledgeStore 条目 id(status='reactivated' 时存在) */
|
|
38
|
+
knowledgeEntryId?: string;
|
|
39
|
+
/** 复活记录实际落盘的知识库根(status='reactivated' 时存在) */
|
|
40
|
+
knowledgeBaseDir?: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface ConstraintsReactivateOptions {
|
|
44
|
+
projectPath?: string;
|
|
45
|
+
reason?: string;
|
|
46
|
+
/** 直达模式显式确认(--yes):与 retire 同一道人确认闸门,无此 flag 直达拒绝执行 */
|
|
47
|
+
yes?: boolean;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* 写 KnowledgeStore 复活记录(consumptionMode: 'signal')
|
|
52
|
+
*
|
|
53
|
+
* baseDir 走 openKnowledgeStore 同一解析点,与 retire 沉淀写口同根。
|
|
54
|
+
*/
|
|
55
|
+
function saveReactivateKnowledge(
|
|
56
|
+
id: string,
|
|
57
|
+
target: RetireTargetInfo,
|
|
58
|
+
reason: string,
|
|
59
|
+
iso: string,
|
|
60
|
+
io: CommandIO
|
|
61
|
+
): { entryId: string; baseDir: string } {
|
|
62
|
+
const entryId = `constraint-reactivated-${id}`;
|
|
63
|
+
const contentLines = [
|
|
64
|
+
`# 约束复活:${id}`,
|
|
65
|
+
'',
|
|
66
|
+
`原退役沉淀:constraint-retired-${id}(不改历史,原条目保留)`,
|
|
67
|
+
'',
|
|
68
|
+
'## 规则原文',
|
|
69
|
+
'',
|
|
70
|
+
target.description ? `description: ${target.description}` : undefined,
|
|
71
|
+
target.rule ? `rule: ${target.rule}` : undefined,
|
|
72
|
+
target.message ? `message: ${target.message}` : undefined,
|
|
73
|
+
'',
|
|
74
|
+
'## 复活原因',
|
|
75
|
+
'',
|
|
76
|
+
reason || '(未填写)',
|
|
77
|
+
'',
|
|
78
|
+
`复活日期: ${iso}`,
|
|
79
|
+
].filter((l): l is string => l !== undefined);
|
|
80
|
+
|
|
81
|
+
const entry: KnowledgeEntry = {
|
|
82
|
+
id: entryId,
|
|
83
|
+
type: 'decision',
|
|
84
|
+
title: `约束复活:${id}`,
|
|
85
|
+
content: contentLines.join('\n'),
|
|
86
|
+
maturity: 'verified',
|
|
87
|
+
layer: 'project',
|
|
88
|
+
created: iso,
|
|
89
|
+
lastReferenced: iso,
|
|
90
|
+
contributors: [],
|
|
91
|
+
projects: [],
|
|
92
|
+
tags: ['constraint-reactivated', `constraint:${id}`, `severity:${target.severity}`],
|
|
93
|
+
applicablePhases: [],
|
|
94
|
+
sourceReferences: [{ timestamp: iso }],
|
|
95
|
+
referencedBy: [],
|
|
96
|
+
executionResults: [],
|
|
97
|
+
consumptionMode: 'signal',
|
|
98
|
+
origin: 'human',
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const store = openKnowledgeStore({}, io);
|
|
102
|
+
store.save(entry);
|
|
103
|
+
return { entryId, baseDir: store.getBaseDir() };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* 执行单条约束复活(纯执行逻辑,无交互)
|
|
108
|
+
*
|
|
109
|
+
* 不存在的 id / 无 retired 墓碑的 id 通过 status 返回,由调用方提示。
|
|
110
|
+
*/
|
|
111
|
+
export function reactivateConstraint(
|
|
112
|
+
projectRoot: string,
|
|
113
|
+
id: string,
|
|
114
|
+
options: ReactivateExecuteOptions = {}
|
|
115
|
+
): ReactivateResult {
|
|
116
|
+
const now = options.now ?? new Date();
|
|
117
|
+
const iso = now.toISOString();
|
|
118
|
+
|
|
119
|
+
const target = findRetireTarget(id, projectRoot);
|
|
120
|
+
if (!target) {
|
|
121
|
+
return { id, status: 'unknown_id' };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// 只认 retired 墓碑:裸 disable 与未配置一律 not_retired(与 retire 的幂等口径对偶)
|
|
125
|
+
const loader = new ProjectConfigLoader(projectRoot);
|
|
126
|
+
loader.load();
|
|
127
|
+
const existing = loader.getConfig().constraints?.[id] as { enabled?: boolean; retired?: unknown } | undefined;
|
|
128
|
+
if (!(existing?.enabled === false && existing.retired)) {
|
|
129
|
+
return { id, status: 'not_retired' };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// 1. 删 config.yml constraints.<id> 墓碑段(恢复 = 回生效集)
|
|
133
|
+
removeYamlEntry(path.join(projectRoot, '.harness', 'config.yml'), 'constraints', id);
|
|
134
|
+
|
|
135
|
+
// 2. KnowledgeStore 复活条目(不改历史,新写一条)
|
|
136
|
+
const { entryId: knowledgeEntryId, baseDir: knowledgeBaseDir } = saveReactivateKnowledge(
|
|
137
|
+
id,
|
|
138
|
+
target,
|
|
139
|
+
options.reason ?? '',
|
|
140
|
+
iso,
|
|
141
|
+
options.io ?? processIO
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
return { id, status: 'reactivated', knowledgeEntryId, knowledgeBaseDir };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* 打印单条复活结果
|
|
149
|
+
*/
|
|
150
|
+
export function printReactivateResult(result: ReactivateResult, io: CommandIO = processIO, projectRoot?: string): CommandResult {
|
|
151
|
+
switch (result.status) {
|
|
152
|
+
case 'unknown_id':
|
|
153
|
+
log(io, chalk.red(`❌ ${result.id}: 约束不存在(内置与应用层约束中都未找到),未做任何变更`));
|
|
154
|
+
return { kind: 'skip', reason: `${result.id}: 约束不存在,未做任何变更` };
|
|
155
|
+
case 'not_retired':
|
|
156
|
+
log(io, chalk.yellow(`⚠️ ${result.id}: 无 retired 墓碑(未退休或仅裸 disable),未做任何变更`));
|
|
157
|
+
return { kind: 'skip', reason: `${result.id}: 无 retired 墓碑,未做任何变更` };
|
|
158
|
+
case 'reactivated': {
|
|
159
|
+
log(io, chalk.green(`✅ ${result.id}: 已复活(config.yml 墓碑段已删,约束回到生效集)`));
|
|
160
|
+
log(io, ` 复活沉淀: ${result.knowledgeEntryId}(${result.knowledgeBaseDir})`);
|
|
161
|
+
if (projectRoot) logCommitHint(projectRoot, io);
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return { kind: 'ok' };
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* CLI handler: harness constraints reactivate <id>
|
|
170
|
+
*
|
|
171
|
+
* 人确认闸门与 retire 同形:直达必须显式 `--yes`,无 `--yes` 报错 + 非零退出码,
|
|
172
|
+
* 不落盘任何文件。
|
|
173
|
+
*/
|
|
174
|
+
export async function constraintsReactivate(
|
|
175
|
+
id?: string,
|
|
176
|
+
options: ConstraintsReactivateOptions = {},
|
|
177
|
+
io: CommandIO = processIO,
|
|
178
|
+
): Promise<CommandResult> {
|
|
179
|
+
const projectRoot = options.projectPath || process.cwd();
|
|
180
|
+
|
|
181
|
+
if (!id) {
|
|
182
|
+
logError(io, chalk.red('❌ 用法:harness constraints reactivate <id> --yes [--reason "..."](复活是点对点操作,无交互模式)'));
|
|
183
|
+
return { kind: 'usage-error', reason: 'reactivate 缺少约束 id,未做任何变更' };
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (!options.yes) {
|
|
187
|
+
logError(io,
|
|
188
|
+
chalk.red(`❌ 直达复活需要显式人确认(与 retire 同一闸门),未做任何变更\n`) +
|
|
189
|
+
` 带 --yes 显式确认直达:harness constraints reactivate ${id} --yes` +
|
|
190
|
+
`${options.reason ? ` --reason "${options.reason}"` : ''}`
|
|
191
|
+
);
|
|
192
|
+
return { kind: 'usage-error', reason: `直达复活 ${id} 缺少显式 --yes 人确认,未做任何变更` };
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const result = reactivateConstraint(projectRoot, id, { reason: options.reason, io });
|
|
196
|
+
return printReactivateResult(result, io, projectRoot);
|
|
197
|
+
}
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* harness constraints report —— 约束使用报告(ADR-0001 决策 3/4,ADR-0029 收窄)
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* 面向"人审约束"场景:
|
|
5
5
|
* - check 约束统计表(total/pass/fail/skip、fail 率、首次/最近触发)
|
|
6
6
|
* - 退役候选诊断(零触发/零拦截/不可评估/高噪),retire 交互模式的数据源
|
|
7
|
+
* - 零拦截观察名单(ADR-0032,块 3 子项 4):零拦截命中先进观察名单挂一个季度,
|
|
8
|
+
* 进 report 但不进退役候选,期满且样本足才转候选;名单状态经 StateIO 读-改-写
|
|
9
|
+
* `.harness/.state.json`(本命令因此不再是纯只读——唯一写点是观察名单列入)
|
|
7
10
|
* - 配置健康(unknownIds 残留提示)
|
|
8
11
|
* - --export [file]:脱敏 markdown 摘要(不含项目路径)
|
|
9
12
|
*/
|
|
@@ -15,10 +18,12 @@ import {
|
|
|
15
18
|
buildConstraintsUsageReport,
|
|
16
19
|
CANDIDATE_KIND_LABEL,
|
|
17
20
|
DEFAULT_DIAGNOSE_THRESHOLDS,
|
|
21
|
+
WATCHLIST_PERIOD_DAYS,
|
|
18
22
|
type ConstraintsUsageReport,
|
|
19
23
|
type ConstraintUsageStats,
|
|
20
24
|
type DiagnoseThresholds,
|
|
21
25
|
} from '../../core/constraints/usage-report';
|
|
26
|
+
import { fileStateIO, type StateIO } from '../state-io';
|
|
22
27
|
import { getConstraintsMeta } from './constraints';
|
|
23
28
|
import { log, processIO, type CommandIO, type CommandResult } from '../command-contract';
|
|
24
29
|
|
|
@@ -33,6 +38,13 @@ export interface ConstraintsReportOptions {
|
|
|
33
38
|
noiseFailRate?: number;
|
|
34
39
|
/** 高噪候选最小评估样本数 */
|
|
35
40
|
noiseMinTotal?: number;
|
|
41
|
+
/**
|
|
42
|
+
* 观察名单状态接缝(缺省 = fileStateIO(projectPath))。
|
|
43
|
+
* 注入则 `.harness/.state.json` 的读写全程走注入面(ADR-0026 同口径)。
|
|
44
|
+
*/
|
|
45
|
+
stateIO?: StateIO;
|
|
46
|
+
/** 观察期判定时钟(Unix ms),测试注入用——别在测试里等 90 天 */
|
|
47
|
+
now?: number;
|
|
36
48
|
}
|
|
37
49
|
|
|
38
50
|
function formatTime(ts: number | undefined): string {
|
|
@@ -94,6 +106,18 @@ export function renderExportMarkdown(
|
|
|
94
106
|
}
|
|
95
107
|
lines.push('');
|
|
96
108
|
|
|
109
|
+
// 观察名单(块 3 子项 4):进报告不进候选,列入时间与剩余观察期随条目带出
|
|
110
|
+
lines.push('## 观察名单');
|
|
111
|
+
lines.push('');
|
|
112
|
+
if (report.watchlist.length === 0) {
|
|
113
|
+
lines.push('(空)');
|
|
114
|
+
} else {
|
|
115
|
+
for (const w of report.watchlist) {
|
|
116
|
+
lines.push(`- \`${w.id}\` — ${w.reason}(列入 ${w.listedAt.slice(0, 10)},剩余观察 ${w.remainingDays} 天;满 ${WATCHLIST_PERIOD_DAYS} 天且样本足转退役候选)`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
lines.push('');
|
|
120
|
+
|
|
97
121
|
if (report.lint.unknownIds.length > 0) {
|
|
98
122
|
lines.push('## 配置健康');
|
|
99
123
|
lines.push('');
|
|
@@ -116,7 +140,16 @@ export async function constraintsReport(options: ConstraintsReportOptions = {},
|
|
|
116
140
|
if (options.noiseFailRate !== undefined) thresholds.highNoiseFailRate = options.noiseFailRate;
|
|
117
141
|
if (options.noiseMinTotal !== undefined) thresholds.highNoiseMinEvaluated = options.noiseMinTotal;
|
|
118
142
|
|
|
119
|
-
const
|
|
143
|
+
const stateIO = options.stateIO ?? fileStateIO(projectRoot);
|
|
144
|
+
const state = stateIO.read();
|
|
145
|
+
const report = buildConstraintsUsageReport(projectRoot, thresholds, {
|
|
146
|
+
watchlist: state.constraintWatchlist,
|
|
147
|
+
now: options.now,
|
|
148
|
+
});
|
|
149
|
+
// 观察名单只增不删:键数变多 = 本次有新列入,经 StateIO 写回(读-改-写,不动其他字段)
|
|
150
|
+
if (Object.keys(report.nextWatchlist).length > Object.keys(state.constraintWatchlist ?? {}).length) {
|
|
151
|
+
stateIO.write({ ...state, constraintWatchlist: report.nextWatchlist });
|
|
152
|
+
}
|
|
120
153
|
|
|
121
154
|
if (options.json) {
|
|
122
155
|
log(io, JSON.stringify(report, null, 2));
|
|
@@ -151,6 +184,15 @@ export async function constraintsReport(options: ConstraintsReportOptions = {},
|
|
|
151
184
|
}
|
|
152
185
|
log(io);
|
|
153
186
|
|
|
187
|
+
// 观察名单分组(零拦截中间态,块 3 子项 4):无条目零噪声
|
|
188
|
+
if (report.watchlist.length > 0) {
|
|
189
|
+
log(io, chalk.bold(`观察名单(${report.watchlist.length} 条,零拦截观察期 ${WATCHLIST_PERIOD_DAYS} 天,期满且样本足才转退役候选):`));
|
|
190
|
+
for (const w of report.watchlist) {
|
|
191
|
+
log(io, ` [观察中] ${w.id} — ${w.reason}(列入 ${w.listedAt.slice(0, 10)},剩余观察 ${w.remainingDays} 天)`);
|
|
192
|
+
}
|
|
193
|
+
log(io);
|
|
194
|
+
}
|
|
195
|
+
|
|
154
196
|
// 配置健康
|
|
155
197
|
if (report.lint.unknownIds.length > 0) {
|
|
156
198
|
log(io, chalk.yellow('⚠️ 配置健康:'));
|
|
@@ -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);
|
|
@@ -340,7 +340,7 @@ export const COMMAND_DEFINITIONS: CommandDefinition[] = [
|
|
|
340
340
|
{
|
|
341
341
|
command: 'retire',
|
|
342
342
|
argument: '[id]',
|
|
343
|
-
description: '退役约束:无 id 进入交互式候选选择(人确认);带 id 直达退役需显式 --yes(写 config.yml
|
|
343
|
+
description: '退役约束:无 id 进入交互式候选选择(人确认);带 id 直达退役需显式 --yes(写 config.yml retired 墓碑 + KnowledgeStore 沉淀)',
|
|
344
344
|
options: [
|
|
345
345
|
{ flags: '-p, --project-path <path>', description: '项目路径' },
|
|
346
346
|
{ flags: '--reason <text>', description: '退役原因' },
|
|
@@ -353,6 +353,36 @@ export const COMMAND_DEFINITIONS: CommandDefinition[] = [
|
|
|
353
353
|
yes: options.yes,
|
|
354
354
|
}],
|
|
355
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
|
+
},
|
|
356
386
|
],
|
|
357
387
|
},
|
|
358
388
|
{
|