@brawnen/agent-harness-cli 0.1.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.
@@ -0,0 +1,316 @@
1
+ import { createTaskFromInput, suspendActiveTask } from "../lib/task-core.js";
2
+ import { normalizeOutputPolicy } from "../lib/output-policy.js";
3
+ import { loadProjectConfig } from "../lib/project-config.js";
4
+ import { confirmTaskContract, resolveTaskId, updateTaskState } from "../lib/state-store.js";
5
+ import { evaluateTaskWorkflowDecision, normalizeWorkflowPolicy } from "../lib/workflow-policy.js";
6
+
7
+ export function runTask(argv) {
8
+ const [subcommand, ...rest] = argv;
9
+
10
+ if (!subcommand) {
11
+ console.error("缺少 task 子命令。可用: intake, suspend-active");
12
+ return 1;
13
+ }
14
+
15
+ if (subcommand === "intake") {
16
+ return runTaskIntake(rest);
17
+ }
18
+
19
+ if (subcommand === "confirm" || subcommand === "confirm-contract") {
20
+ return runTaskConfirm(rest);
21
+ }
22
+
23
+ if (subcommand === "suspend-active" || subcommand === "suspend-active-task") {
24
+ return runTaskSuspendActive(rest);
25
+ }
26
+
27
+ console.error(`未知 task 子命令: ${subcommand}。可用: intake, confirm, suspend-active`);
28
+ return 1;
29
+ }
30
+
31
+ function runTaskIntake(argv) {
32
+ const parsed = parseTaskIntakeArgs(argv);
33
+ if (!parsed.ok) {
34
+ console.error(parsed.error);
35
+ return 1;
36
+ }
37
+
38
+ try {
39
+ const result = createTaskFromInput(process.cwd(), parsed.options.input, parsed.options);
40
+ printJson(result);
41
+ return 0;
42
+ } catch (error) {
43
+ console.error(error.message);
44
+ return 1;
45
+ }
46
+ }
47
+
48
+ function runTaskSuspendActive(argv) {
49
+ const parsed = parseTaskSuspendArgs(argv);
50
+ if (!parsed.ok) {
51
+ console.error(parsed.error);
52
+ return 1;
53
+ }
54
+
55
+ try {
56
+ const result = suspendActiveTask(process.cwd(), {
57
+ reason: parsed.options.reason,
58
+ clearActive: true
59
+ });
60
+ printJson(result);
61
+ return 0;
62
+ } catch (error) {
63
+ console.error(error.message);
64
+ return 1;
65
+ }
66
+ }
67
+
68
+ function runTaskConfirm(argv) {
69
+ const parsed = parseTaskConfirmArgs(argv);
70
+ if (!parsed.ok) {
71
+ console.error(parsed.error);
72
+ return 1;
73
+ }
74
+
75
+ try {
76
+ const cwd = process.cwd();
77
+ const taskId = resolveTaskId(cwd, parsed.options.taskId);
78
+ const confirmed = confirmTaskContract(cwd, taskId, parsed.options);
79
+ const projectConfig = loadProjectConfig(cwd);
80
+ const workflowDecision = evaluateTaskWorkflowDecision(confirmed, {
81
+ workflowPolicy: normalizeWorkflowPolicy(projectConfig?.workflow_policy),
82
+ outputPolicy: normalizeOutputPolicy(projectConfig?.output_policy),
83
+ previousDecision: confirmed.workflow_decision
84
+ });
85
+ const result = updateTaskState(cwd, taskId, {
86
+ workflow_decision: workflowDecision
87
+ });
88
+ printJson(result);
89
+ return 0;
90
+ } catch (error) {
91
+ console.error(error.message);
92
+ return 1;
93
+ }
94
+ }
95
+
96
+ function parseTaskIntakeArgs(argv) {
97
+ const options = {
98
+ acceptance: [],
99
+ assumptions: [],
100
+ constraints: [],
101
+ contextRefs: [],
102
+ goal: null,
103
+ input: null,
104
+ intent: null,
105
+ mode: null,
106
+ scope: [],
107
+ suspendActive: false,
108
+ suspendReason: null,
109
+ taskId: null,
110
+ title: null
111
+ };
112
+
113
+ const positional = [];
114
+
115
+ for (let index = 0; index < argv.length; index += 1) {
116
+ const arg = argv[index];
117
+
118
+ if (!arg.startsWith("--")) {
119
+ positional.push(arg);
120
+ continue;
121
+ }
122
+
123
+ if (arg === "--input") {
124
+ options.input = argv[index + 1] ?? null;
125
+ index += 1;
126
+ continue;
127
+ }
128
+ if (arg === "--intent") {
129
+ options.intent = argv[index + 1] ?? null;
130
+ index += 1;
131
+ continue;
132
+ }
133
+ if (arg === "--goal") {
134
+ options.goal = argv[index + 1] ?? null;
135
+ index += 1;
136
+ continue;
137
+ }
138
+ if (arg === "--scope") {
139
+ options.scope.push(argv[index + 1] ?? "");
140
+ index += 1;
141
+ continue;
142
+ }
143
+ if (arg === "--acceptance") {
144
+ options.acceptance.push(argv[index + 1] ?? "");
145
+ index += 1;
146
+ continue;
147
+ }
148
+ if (arg === "--constraint") {
149
+ options.constraints.push(argv[index + 1] ?? "");
150
+ index += 1;
151
+ continue;
152
+ }
153
+ if (arg === "--assumption") {
154
+ options.assumptions.push(argv[index + 1] ?? "");
155
+ index += 1;
156
+ continue;
157
+ }
158
+ if (arg === "--context-ref") {
159
+ options.contextRefs.push(argv[index + 1] ?? "");
160
+ index += 1;
161
+ continue;
162
+ }
163
+ if (arg === "--mode") {
164
+ options.mode = argv[index + 1] ?? null;
165
+ index += 1;
166
+ continue;
167
+ }
168
+ if (arg === "--task-id") {
169
+ options.taskId = argv[index + 1] ?? null;
170
+ index += 1;
171
+ continue;
172
+ }
173
+ if (arg === "--title") {
174
+ options.title = argv[index + 1] ?? null;
175
+ index += 1;
176
+ continue;
177
+ }
178
+ if (arg === "--suspend-active") {
179
+ options.suspendActive = true;
180
+ continue;
181
+ }
182
+ if (arg === "--suspend-reason") {
183
+ options.suspendReason = argv[index + 1] ?? null;
184
+ index += 1;
185
+ continue;
186
+ }
187
+
188
+ return { ok: false, error: `未知参数: ${arg}` };
189
+ }
190
+
191
+ if (!options.input && positional.length > 0) {
192
+ options.input = positional.join(" ");
193
+ }
194
+
195
+ if (!options.input) {
196
+ return { ok: false, error: "需要任务描述。用法: task intake \"<任务描述>\" 或 --input" };
197
+ }
198
+
199
+ return { ok: true, options };
200
+ }
201
+
202
+ function parseTaskSuspendArgs(argv) {
203
+ const options = {
204
+ reason: "用户显式要求挂起当前活跃任务。"
205
+ };
206
+
207
+ for (let index = 0; index < argv.length; index += 1) {
208
+ const arg = argv[index];
209
+
210
+ if (arg === "--reason") {
211
+ options.reason = argv[index + 1] ?? null;
212
+ index += 1;
213
+ continue;
214
+ }
215
+
216
+ return { ok: false, error: `未知参数: ${arg}` };
217
+ }
218
+
219
+ return { ok: true, options };
220
+ }
221
+
222
+ function parseTaskConfirmArgs(argv) {
223
+ const options = {
224
+ acceptance: [],
225
+ constraints: [],
226
+ contextRefs: [],
227
+ evidenceRequired: [],
228
+ goal: null,
229
+ id: null,
230
+ intent: null,
231
+ mode: null,
232
+ riskLevel: null,
233
+ scope: [],
234
+ taskId: null,
235
+ title: null,
236
+ verification: []
237
+ };
238
+
239
+ for (let index = 0; index < argv.length; index += 1) {
240
+ const arg = argv[index];
241
+
242
+ if (arg === "--task-id") {
243
+ options.taskId = argv[index + 1] ?? null;
244
+ index += 1;
245
+ continue;
246
+ }
247
+ if (arg === "--intent") {
248
+ options.intent = argv[index + 1] ?? null;
249
+ index += 1;
250
+ continue;
251
+ }
252
+ if (arg === "--goal") {
253
+ options.goal = argv[index + 1] ?? null;
254
+ index += 1;
255
+ continue;
256
+ }
257
+ if (arg === "--scope") {
258
+ options.scope.push(argv[index + 1] ?? "");
259
+ index += 1;
260
+ continue;
261
+ }
262
+ if (arg === "--acceptance") {
263
+ options.acceptance.push(argv[index + 1] ?? "");
264
+ index += 1;
265
+ continue;
266
+ }
267
+ if (arg === "--constraint") {
268
+ options.constraints.push(argv[index + 1] ?? "");
269
+ index += 1;
270
+ continue;
271
+ }
272
+ if (arg === "--context-ref") {
273
+ options.contextRefs.push(argv[index + 1] ?? "");
274
+ index += 1;
275
+ continue;
276
+ }
277
+ if (arg === "--verification") {
278
+ options.verification.push(argv[index + 1] ?? "");
279
+ index += 1;
280
+ continue;
281
+ }
282
+ if (arg === "--evidence-required") {
283
+ options.evidenceRequired.push(argv[index + 1] ?? "");
284
+ index += 1;
285
+ continue;
286
+ }
287
+ if (arg === "--mode") {
288
+ options.mode = argv[index + 1] ?? null;
289
+ index += 1;
290
+ continue;
291
+ }
292
+ if (arg === "--title") {
293
+ options.title = argv[index + 1] ?? null;
294
+ index += 1;
295
+ continue;
296
+ }
297
+ if (arg === "--id") {
298
+ options.id = argv[index + 1] ?? null;
299
+ index += 1;
300
+ continue;
301
+ }
302
+ if (arg === "--risk-level") {
303
+ options.riskLevel = argv[index + 1] ?? null;
304
+ index += 1;
305
+ continue;
306
+ }
307
+
308
+ return { ok: false, error: `未知参数: ${arg}` };
309
+ }
310
+
311
+ return { ok: true, options };
312
+ }
313
+
314
+ function printJson(value) {
315
+ console.log(`${JSON.stringify(value, null, 2)}\n`);
316
+ }
@@ -0,0 +1,173 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+
4
+ import { loadProjectConfig } from "../lib/project-config.js";
5
+ import { resolveTaskId, requireTaskState } from "../lib/state-store.js";
6
+
7
+ const VERIFICATION_MATRIX = {
8
+ bug: {
9
+ description: "至少一条命令或测试证明问题不再复现",
10
+ deterministic: true,
11
+ requiredTypes: ["command_result", "test_result"]
12
+ },
13
+ feature: {
14
+ description: "至少一条命令或验证动作证明新能力可运行",
15
+ deterministic: true,
16
+ requiredTypes: ["command_result"]
17
+ },
18
+ refactor: {
19
+ description: "至少一条测试证明行为未破坏",
20
+ deterministic: true,
21
+ requiredTypes: ["test_result"]
22
+ },
23
+ explore: {
24
+ description: "至少有结论、依据、风险与下一步建议",
25
+ deterministic: false,
26
+ requiredTypes: ["reasoning_note"]
27
+ },
28
+ prototype: {
29
+ description: "可无强制验证,但必须明确未验证范围",
30
+ deterministic: false,
31
+ requiredTypes: ["reasoning_note"]
32
+ }
33
+ };
34
+
35
+ export function runVerify(argv) {
36
+ const parsed = parseVerifyArgs(argv);
37
+ if (!parsed.ok) {
38
+ console.error(parsed.error);
39
+ return 1;
40
+ }
41
+
42
+ let taskState;
43
+ const cwd = process.cwd();
44
+ try {
45
+ const taskId = parsed.options.taskFile
46
+ ? null
47
+ : resolveTaskId(cwd, parsed.options.taskId);
48
+ taskState = parsed.options.taskFile
49
+ ? readTaskFile(cwd, parsed.options.taskFile)
50
+ : requireTaskState(cwd, taskId);
51
+ } catch (error) {
52
+ console.error(error.message);
53
+ return 1;
54
+ }
55
+
56
+ const projectConfig = loadProjectConfig(cwd);
57
+ const result = verifyTaskState(taskState, {
58
+ reportPolicy: normalizeReportPolicy(projectConfig?.output_policy?.report)
59
+ });
60
+ console.log(`${JSON.stringify(result, null, 2)}\n`);
61
+ return result.allowed ? 0 : 1;
62
+ }
63
+
64
+ function parseVerifyArgs(argv) {
65
+ const options = {
66
+ taskFile: null,
67
+ taskId: null
68
+ };
69
+
70
+ for (let index = 0; index < argv.length; index += 1) {
71
+ const arg = argv[index];
72
+
73
+ if (arg === "--task-id") {
74
+ const value = argv[index + 1];
75
+ if (!value) {
76
+ return { ok: false, error: "缺少 --task-id 参数值" };
77
+ }
78
+ options.taskId = value;
79
+ index += 1;
80
+ continue;
81
+ }
82
+
83
+ if (arg === "--task-file") {
84
+ const value = argv[index + 1];
85
+ if (!value) {
86
+ return { ok: false, error: "缺少 --task-file 参数值" };
87
+ }
88
+ options.taskFile = value;
89
+ index += 1;
90
+ continue;
91
+ }
92
+
93
+ return { ok: false, error: `未知参数: ${arg}` };
94
+ }
95
+
96
+ if (options.taskId && options.taskFile) {
97
+ return { ok: false, error: "--task-id 与 --task-file 不能同时使用" };
98
+ }
99
+
100
+ return { ok: true, options };
101
+ }
102
+
103
+ export function verifyTaskState(taskState, options = {}) {
104
+ const intent = taskState?.confirmed_contract?.intent ?? taskState?.task_draft?.intent ?? "unknown";
105
+ const acceptance = taskState?.confirmed_contract?.acceptance ?? taskState?.task_draft?.acceptance ?? [];
106
+ const evidence = Array.isArray(taskState?.evidence) ? taskState.evidence : [];
107
+ const openQuestions = Array.isArray(taskState?.open_questions) ? taskState.open_questions : [];
108
+ const missingEvidence = [];
109
+ const reportPolicy = normalizeReportPolicy(options.reportPolicy);
110
+
111
+ if (openQuestions.length > 0) {
112
+ missingEvidence.push(`存在未关闭的阻断问题: ${openQuestions[0]}`);
113
+ }
114
+
115
+ const matrix = VERIFICATION_MATRIX[intent];
116
+ if (!matrix) {
117
+ missingEvidence.push(`未知的 intent 类型: ${intent},无法匹配验证矩阵`);
118
+ } else {
119
+ const hasRequired = matrix.requiredTypes.some((type) => evidence.some((item) => item.type === type));
120
+ if (!hasRequired) {
121
+ missingEvidence.push(`${matrix.description}(需要: ${matrix.requiredTypes.join(" 或 ")})`);
122
+ }
123
+
124
+ if (matrix.deterministic) {
125
+ const deterministicEvidence = evidence.filter((item) => matrix.requiredTypes.includes(item.type));
126
+ const allFailed =
127
+ deterministicEvidence.length > 0 &&
128
+ deterministicEvidence.every((item) => item.passed === false || (typeof item.exit_code === "number" && item.exit_code !== 0));
129
+
130
+ if (allFailed) {
131
+ missingEvidence.push("已有验证结果但全部失败");
132
+ }
133
+ }
134
+ }
135
+
136
+ if (!Array.isArray(acceptance) || acceptance.length === 0) {
137
+ missingEvidence.push("未定义 acceptance 标准");
138
+ }
139
+
140
+ return {
141
+ allowed: missingEvidence.length === 0,
142
+ intent,
143
+ missing_evidence: missingEvidence,
144
+ report_policy: reportPolicy,
145
+ signal: missingEvidence.length === 0 ? "allow_completion" : "block_completion",
146
+ task_id: taskState.task_id ?? null
147
+ };
148
+ }
149
+
150
+ function readTaskFile(cwd, taskFile) {
151
+ const filePath = path.resolve(cwd, taskFile);
152
+ if (!fs.existsSync(filePath)) {
153
+ throw new Error(`文件不存在: ${filePath}`);
154
+ }
155
+
156
+ try {
157
+ return JSON.parse(fs.readFileSync(filePath, "utf8"));
158
+ } catch {
159
+ throw new Error(`JSON 解析失败: ${filePath}`);
160
+ }
161
+ }
162
+
163
+ function normalizeReportPolicy(value) {
164
+ const policy = value && typeof value === "object" && !Array.isArray(value) ? value : {};
165
+ return {
166
+ required: policy.required !== false,
167
+ format: typeof policy.format === "string" ? policy.format : "json",
168
+ directory: typeof policy.directory === "string" ? policy.directory : ".harness/reports",
169
+ required_sections: Array.isArray(policy.required_sections)
170
+ ? policy.required_sections.map((item) => String(item))
171
+ : []
172
+ };
173
+ }
package/src/index.js ADDED
@@ -0,0 +1,101 @@
1
+ import { runAudit } from "./commands/audit.js";
2
+ import { runDelivery } from "./commands/delivery.js";
3
+ import { runDocs } from "./commands/docs.js";
4
+ import { runGate } from "./commands/gate.js";
5
+ import { runInit } from "./commands/init.js";
6
+ import { runReport } from "./commands/report.js";
7
+ import { runState } from "./commands/state.js";
8
+ import { runStatus } from "./commands/status.js";
9
+ import { runTask } from "./commands/task.js";
10
+ import { runVerify } from "./commands/verify.js";
11
+
12
+ const HELP_TEXT = `agent-harness CLI
13
+
14
+ Usage:
15
+ agent-harness --help
16
+ agent-harness --version
17
+ agent-harness init
18
+ agent-harness init --dry-run
19
+ agent-harness init --protocol-only
20
+ agent-harness audit <append|read>
21
+ agent-harness delivery <ready|request|commit>
22
+ agent-harness docs scaffold --type <design-note|adr>
23
+ agent-harness gate before-tool --tool <tool>
24
+ agent-harness status
25
+ agent-harness task intake "<任务描述>"
26
+ agent-harness task confirm [--task-id <task-id>]
27
+ agent-harness task suspend-active
28
+ agent-harness state <init|get|update|active>
29
+ agent-harness verify
30
+ agent-harness verify --task-id <task-id>
31
+ agent-harness report --conclusion <text>
32
+
33
+ Options:
34
+ --host <auto|claude-code|codex|gemini-cli>
35
+ --rules <base|full>
36
+ --mode <delivery|explore|poc>
37
+ --dry-run
38
+ --protocol-only
39
+ --force
40
+
41
+ Status:
42
+ task/init/status/state/verify/report/gate/audit/delivery/docs MVP are implemented.
43
+ `;
44
+
45
+ export function run(argv) {
46
+ const [command] = argv;
47
+
48
+ if (!command || command === "--help" || command === "-h") {
49
+ console.log(HELP_TEXT);
50
+ return 0;
51
+ }
52
+
53
+ if (command === "--version" || command === "-v") {
54
+ console.log("0.1.0");
55
+ return 0;
56
+ }
57
+
58
+ if (command === "init") {
59
+ return runInit(argv.slice(1));
60
+ }
61
+
62
+ if (command === "audit") {
63
+ return runAudit(argv.slice(1));
64
+ }
65
+
66
+ if (command === "delivery") {
67
+ return runDelivery(argv.slice(1));
68
+ }
69
+
70
+ if (command === "docs") {
71
+ return runDocs(argv.slice(1));
72
+ }
73
+
74
+ if (command === "gate") {
75
+ return runGate(argv.slice(1));
76
+ }
77
+
78
+ if (command === "status") {
79
+ return runStatus(argv.slice(1));
80
+ }
81
+
82
+ if (command === "task") {
83
+ return runTask(argv.slice(1));
84
+ }
85
+
86
+ if (command === "state") {
87
+ return runState(argv.slice(1));
88
+ }
89
+
90
+ if (command === "verify") {
91
+ return runVerify(argv.slice(1));
92
+ }
93
+
94
+ if (command === "report") {
95
+ return runReport(argv.slice(1));
96
+ }
97
+
98
+ console.error(`未知命令: ${command}`);
99
+ console.error("运行 `agent-harness --help` 查看可用命令。");
100
+ return 1;
101
+ }
@@ -0,0 +1,80 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+
4
+ import { runtimePath } from "./runtime-paths.js";
5
+
6
+ const VALID_EVENT_TYPES = [
7
+ "force_override",
8
+ "gate_violation",
9
+ "remediation",
10
+ "state_recovery",
11
+ "manual_confirmation"
12
+ ];
13
+ const VALID_PHASES = ["intake", "clarify", "plan", "execute", "verify", "report", "close"];
14
+ const VALID_RISK_LEVELS = ["low", "medium", "high", "unknown"];
15
+
16
+ export function appendAuditEntry(cwd, entry) {
17
+ validateEntry(entry);
18
+
19
+ const nextEntry = {
20
+ event_type: entry.event_type,
21
+ task_id: entry.task_id,
22
+ phase: entry.phase,
23
+ signal: entry.signal,
24
+ description: entry.description,
25
+ user_input: entry.user_input ?? null,
26
+ risk_at_time: entry.risk_at_time ?? "unknown",
27
+ timestamp: entry.timestamp ?? new Date().toISOString()
28
+ };
29
+
30
+ const auditDir = auditDirPath(cwd);
31
+ fs.mkdirSync(auditDir, { recursive: true });
32
+ const filePath = path.join(auditDir, `${entry.task_id}.jsonl`);
33
+ fs.appendFileSync(filePath, `${JSON.stringify(nextEntry)}\n`, "utf8");
34
+ return nextEntry;
35
+ }
36
+
37
+ export function readAuditEntries(cwd, taskId) {
38
+ const filePath = path.join(auditDirPath(cwd), `${taskId}.jsonl`);
39
+ if (!fs.existsSync(filePath)) {
40
+ return [];
41
+ }
42
+
43
+ return fs
44
+ .readFileSync(filePath, "utf8")
45
+ .split("\n")
46
+ .map((line) => line.trim())
47
+ .filter(Boolean)
48
+ .map((line) => JSON.parse(line));
49
+ }
50
+
51
+ function auditDirPath(cwd) {
52
+ return runtimePath(cwd, "audit");
53
+ }
54
+
55
+ function validateEntry(entry) {
56
+ if (!VALID_EVENT_TYPES.includes(entry.event_type)) {
57
+ throw new Error(`无效的 event_type: ${entry.event_type}`);
58
+ }
59
+
60
+ if (!VALID_PHASES.includes(entry.phase)) {
61
+ throw new Error(`无效的 phase: ${entry.phase}`);
62
+ }
63
+
64
+ const riskLevel = entry.risk_at_time ?? "unknown";
65
+ if (!VALID_RISK_LEVELS.includes(riskLevel)) {
66
+ throw new Error(`无效的 risk_at_time: ${riskLevel}`);
67
+ }
68
+
69
+ if (!entry.task_id) {
70
+ throw new Error("缺少 task_id");
71
+ }
72
+
73
+ if (!entry.signal) {
74
+ throw new Error("缺少 signal");
75
+ }
76
+
77
+ if (!entry.description) {
78
+ throw new Error("缺少 description");
79
+ }
80
+ }