@guanlin4924/devflow-pi 0.1.15 → 0.1.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/package.json +1 -1
  2. package/plugins/devflow-core/extensions/devflow-core.ts +425 -54
  3. package/plugins/devflow-core/skills/workflow-orchestrator/SKILL.md +3 -2
  4. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/architecture_assessment.rb +458 -0
  5. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/architecture_brief.rb +256 -0
  6. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/architecture_constraints.rb +155 -0
  7. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/architecture_feasibility.rb +176 -0
  8. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/control_io.rb +46 -0
  9. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/execution_contract.rb +94 -0
  10. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/init_state.rb +2 -1
  11. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/machine_evidence.rb +112 -0
  12. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/requirement_graph.rb +646 -0
  13. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/runtime_context_collector.rb +210 -0
  14. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/stale_check.rb +56 -0
  15. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/static_runtime_context_collector.rb +518 -0
  16. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/validate_state.rb +140 -0
  17. package/plugins/devflow-core/skills/workflow-orchestrator/scripts/workload_constraint.rb +57 -0
  18. package/plugins/devflow-core/skills/workflow-orchestrator/templates/feature-development.yaml +88 -2
  19. package/plugins/devflow-core/skills/workflow-pipeline/SKILL.md +3 -2
  20. package/plugins/devflow-core/skills/workflow-project-context/SKILL.md +17 -1
  21. package/plugins/devflow-design/skills/workflow-architecture-feasibility/SKILL.md +37 -0
  22. package/plugins/devflow-design/skills/workflow-architecture-feasibility/agents/openai.yaml +4 -0
  23. package/plugins/devflow-design/skills/workflow-backend-tech-spec/SKILL.md +7 -2
  24. package/plugins/devflow-design/skills/workflow-frontend-tech-spec/SKILL.md +6 -1
  25. package/plugins/devflow-requirements/skills/requirement-feasibility-review/SKILL.md +4 -0
  26. package/plugins/devflow-requirements/skills/workflow-domain-modeling/SKILL.md +4 -0
  27. package/plugins/devflow-requirements/skills/workflow-requirement-baseline/SKILL.md +4 -0
  28. package/plugins/devflow-requirements/skills/workflow-requirement-triage/SKILL.md +4 -0
  29. package/src/cli.ts +2 -1
  30. package/src/runtime.ts +2 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@guanlin4924/devflow-pi",
3
- "version": "0.1.15",
3
+ "version": "0.1.17",
4
4
  "type": "module",
5
5
  "description": "Devflow 专项研发 Pi,基于固定版本 OMP Runtime 组装研发状态机、代码智能、设计、实现与质量门禁。",
6
6
  "license": "UNLICENSED",
@@ -1,6 +1,6 @@
1
1
  import { createHash, randomUUID } from "node:crypto";
2
2
  import { existsSync, lstatSync, readFileSync, readdirSync, realpathSync } from "node:fs";
3
- import { mkdir, rename, writeFile } from "node:fs/promises";
3
+ import { mkdir, writeFile } from "node:fs/promises";
4
4
  import path from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
6
  import type { ExtensionAPI } from "@oh-my-pi/pi-coding-agent";
@@ -20,9 +20,17 @@ type ActiveExecutionContract = {
20
20
  contractId: string;
21
21
  feature: string;
22
22
  step: string;
23
+ stateRevision?: number;
24
+ graphRevision?: number;
23
25
  allowedPaths: string[];
24
26
  allowedAgents: string[];
27
+ allowedTools: string[];
28
+ checkerIds: string[];
29
+ toolBudget: number;
30
+ frozenHash: string;
31
+ contractPath?: string;
25
32
  acceptance: string[];
33
+ allowedGraphDelta: string[];
26
34
  onUnknown: "block_and_escalate";
27
35
  };
28
36
 
@@ -198,7 +206,7 @@ const CONTROL_PATHS = [
198
206
  ".planning/artifacts/index.yaml",
199
207
  ];
200
208
 
201
- const CONTROL_PREFIXES = [".planning/workflows/", ".planning/contracts/"];
209
+ const CONTROL_PREFIXES = [".planning/workflows/", ".planning/contracts/", ".planning/evidence/"];
202
210
 
203
211
  const REQUIRED_EXTENSION_TOOLS = [
204
212
  "devflow_doctor",
@@ -206,6 +214,10 @@ const REQUIRED_EXTENSION_TOOLS = [
206
214
  "devflow_bootstrap",
207
215
  "devflow_transition",
208
216
  "devflow_artifact",
217
+ "devflow_machine_evidence",
218
+ "devflow_requirement_graph",
219
+ "devflow_evidence",
220
+ "devflow_contract",
209
221
  "devflow_recovery",
210
222
  "devflow_skill_script",
211
223
  "devflow_context_sync",
@@ -276,6 +288,9 @@ const REQUIRED_SCRIPTS = [
276
288
  "workflow-orchestrator/scripts/inspect_state.rb",
277
289
  "workflow-orchestrator/scripts/advance_state.rb",
278
290
  "workflow-orchestrator/scripts/sync_artifacts.rb",
291
+ "workflow-orchestrator/scripts/requirement_graph.rb",
292
+ "workflow-orchestrator/scripts/machine_evidence.rb",
293
+ "workflow-orchestrator/scripts/execution_contract.rb",
279
294
  "workflow-code-index/scripts/code_index.py",
280
295
  "workflow-context-sync/scripts/context_sync_cursor.py",
281
296
  ];
@@ -380,6 +395,19 @@ function safeControlPath(cwd: string, raw: string | undefined, kind: "state" | "
380
395
  return relative;
381
396
  }
382
397
 
398
+ function isCanonicalRequirementGraphPath(filePath: string): boolean {
399
+ return /^\.planning\/requirements\/[^/\\\x00-\x1F\x7F]+\/(?:governance|evidence|definitions|behavior-graph|requirement-graph|slices|acceptance|traceability|graph\.meta)\.json$/u.test(filePath)
400
+ || /^\.planning\/requirements\/[^/\\\x00-\x1F\x7F]+\/(?:context\/[^/\\]+\.json|control-events\.jsonl)$/u.test(filePath);
401
+ }
402
+
403
+ function isCoreOwnedGovernancePath(filePath: string): boolean {
404
+ // 规范需求图谱只能由 Core 的 devflow_requirement_graph writer 写入;the
405
+ // same ownership extends to its views, machine evidence, and audit records.
406
+ return isCanonicalRequirementGraphPath(filePath)
407
+ || /^\.planning\/evidence\/[^/\\\x00-\x1F\x7F]+\/[^/\\]+\.machine\.json$/u.test(filePath)
408
+ || /^\.planning\/contracts\/[^/\\\x00-\x1F\x7F]+\/(?:EC-[A-Fa-f0-9]+\.json|control-events\.jsonl)$/u.test(filePath);
409
+ }
410
+
383
411
  function resolveIntelSkillRoot(cwd: string): string | undefined {
384
412
  if (isEmbeddedRuntime()) {
385
413
  const packaged = path.join(resolvePluginRoot(), "devflow-intel", "skills");
@@ -641,10 +669,10 @@ function isShellControlMutation(toolName: string, input: unknown): boolean {
641
669
  const command = String((input as Record<string, unknown>).command ?? (input as Record<string, unknown>).cmd ?? "");
642
670
  if (!command) return false;
643
671
  const invokesWriter =
644
- /workflow-orchestrator\/scripts\/(?:advance_state|init_state|init_workflow|sync_artifacts)\.rb[^\n]*--write/.test(
672
+ /workflow-orchestrator\/scripts\/(?:advance_state|init_state|init_workflow|sync_artifacts|requirement_graph|machine_evidence|execution_contract)\.rb[^\n]*--write/.test(
645
673
  command,
646
674
  );
647
- const namesControlPath = [...CONTROL_PATHS, ...CONTROL_PREFIXES].some(item => command.includes(item));
675
+ const namesControlPath = [...CONTROL_PATHS, ...CONTROL_PREFIXES].some(item => command.includes(item)) || /\.planning\/requirements\//.test(command);
648
676
  const looksLikeWrite = /(?:^|[;&|]\s*)(?:rm|mv|cp|install|truncate)\b|(?:^|\s)(?:tee|sed\s+-i|perl\s+-i)\b|>{1,2}/.test(
649
677
  command,
650
678
  );
@@ -668,6 +696,7 @@ export default function devflowCore(pi: ExtensionAPI) {
668
696
  let sessionId = randomUUID();
669
697
  let controlState: SessionControlState = initialControlState();
670
698
  let activeContract: ActiveExecutionContract | undefined;
699
+ let contractAgentCalls = 0;
671
700
 
672
701
  function setControlState(ctx: { ui: { setStatus(key: string, text: string | undefined): void } }, state: SessionControlState) {
673
702
  controlState = state;
@@ -686,18 +715,14 @@ export default function devflowCore(pi: ExtensionAPI) {
686
715
  return contract.allowedPaths.some(root => filePath === root || filePath.startsWith(`${root}/`));
687
716
  }
688
717
 
689
- async function persistContract(cwd: string, contract: ActiveExecutionContract): Promise<string> {
690
- assertRuntimeProjectBoundary(cwd);
691
- const feature = normalizeContractSegment(contract.feature, "feature");
692
- const contractId = normalizeContractSegment(contract.contractId, "contractId");
693
- const root = path.join(cwd, ".planning", "contracts", feature);
694
- const destination = path.join(root, `${contractId}.json`);
695
- await mkdir(root, { recursive: true });
696
- const payload = JSON.stringify({ schemaVersion: 1, activatedAt: new Date().toISOString(), ...contract }, null, 2) + "\n";
697
- const temporary = `${destination}.${randomUUID()}.tmp`;
698
- await writeFile(temporary, payload, "utf8");
699
- await rename(temporary, destination);
700
- return path.relative(cwd, destination).replaceAll("\\", "/");
718
+ async function contractIsFresh(cwd: string, contract: ActiveExecutionContract): Promise<boolean> {
719
+ const state = parseJsonResult(await runRuby(cwd, "workflow-orchestrator/scripts/inspect_state.rb", ["--json"]));
720
+ const stateRevision = typeof state?.revision === "number" ? state.revision : undefined;
721
+ const graphResult = parseJsonResult(await runRuby(cwd, "workflow-orchestrator/scripts/requirement_graph.rb", ["inspect", "--feature", contract.feature, "--json"]));
722
+ const graph = graphResult?.graph;
723
+ const graphRevision = graph && typeof graph === "object" && typeof (graph as Record<string, unknown>).revision === "number"
724
+ ? (graph as Record<string, number>).revision : undefined;
725
+ return stateRevision === contract.stateRevision && graphRevision === contract.graphRevision;
701
726
  }
702
727
 
703
728
  async function collectProjectInitialization(ctx: {
@@ -1079,6 +1104,14 @@ export default function devflowCore(pi: ExtensionAPI) {
1079
1104
 
1080
1105
  pi.on("tool_call", async (event, ctx) => {
1081
1106
  const toolName = String(event.toolName).toLowerCase();
1107
+ if (activeContract && controlState.phase === "executing" && !toolName.startsWith("devflow_")) {
1108
+ if (!activeContract.allowedTools.includes(toolName)) {
1109
+ return { block: true, reason: `Active contract ${activeContract.contractId} does not allow tool: ${toolName}` };
1110
+ }
1111
+ if (!(await contractIsFresh(ctx.cwd, activeContract))) {
1112
+ return { block: true, reason: `Active contract ${activeContract.contractId} is stale against STATE or graph revision; synthesize a new contract before continuing.` };
1113
+ }
1114
+ }
1082
1115
  if (toolName === "task") {
1083
1116
  const requested = requestedTaskAgents(event.input);
1084
1117
  if (requested.missing) {
@@ -1109,8 +1142,12 @@ export default function devflowCore(pi: ExtensionAPI) {
1109
1142
  controlState.phase === "executing" &&
1110
1143
  !requested.names.every(agent => activeContract?.allowedAgents.includes(agent))
1111
1144
  ) {
1112
- return { block: true, reason: `Active contract ${activeContract.contractId} only allows agents: ${activeContract.allowedAgents.join(", ")}` };
1145
+ return { block: true, reason: `Active contract ${activeContract.contractId} only allows agents: ${activeContract.allowedAgents.join(", ")}` };
1146
+ }
1147
+ if (activeContract && controlState.phase === "executing" && contractAgentCalls >= activeContract.toolBudget) {
1148
+ return { block: true, reason: `Active contract ${activeContract.contractId} exhausted its fixed agent budget (${activeContract.toolBudget}).` };
1113
1149
  }
1150
+ if (activeContract && controlState.phase === "executing") contractAgentCalls += 1;
1114
1151
  } else if (
1115
1152
  controlState.phase === "planning" &&
1116
1153
  toolName !== "devflow_contract" &&
@@ -1129,6 +1166,16 @@ export default function devflowCore(pi: ExtensionAPI) {
1129
1166
  "Devflow 状态迁移只能由运行时在用户明确批准已展示的步骤或 checkpoint 后执行;LLM 不得自行推进。",
1130
1167
  };
1131
1168
  }
1169
+ if (
1170
+ !toolName.startsWith("devflow_") &&
1171
+ ["write", "edit", "apply_patch", "notebook_edit", "bash", "shell", "exec_command"].includes(toolName) &&
1172
+ inputPaths(event.input, ctx.cwd).some(isCoreOwnedGovernancePath)
1173
+ ) {
1174
+ return {
1175
+ block: true,
1176
+ reason: "规范需求图谱、派生视图、机器 Evidence 与合同审计只能由 Core writer 写入;Skill 只能提交候选 delta 或 agent_claim。",
1177
+ };
1178
+ }
1132
1179
  if (toolName.startsWith("devflow_")) return;
1133
1180
  if (
1134
1181
  activeContract &&
@@ -1234,6 +1281,319 @@ export default function devflowCore(pi: ExtensionAPI) {
1234
1281
  }
1235
1282
  });
1236
1283
 
1284
+ pi.registerTool({
1285
+ name: "devflow_machine_evidence",
1286
+ label: "Devflow Machine Evidence",
1287
+ description: "Collect Core-owned evidence from the current workflow step's actual required outputs and graph revision.",
1288
+ parameters: z.object({ feature: z.string().min(1), step: z.string().min(1) }),
1289
+ approval: "write",
1290
+ async execute(_id, params, _signal, _onUpdate, ctx) {
1291
+ return renderResult(
1292
+ await runRuby(ctx.cwd, "workflow-orchestrator/scripts/machine_evidence.rb", ["--feature", params.feature, "--step", params.step, "--write"]),
1293
+ { feature: params.feature, step: params.step },
1294
+ );
1295
+ },
1296
+ });
1297
+
1298
+ pi.registerTool({
1299
+ name: "devflow_requirement_graph",
1300
+ label: "Devflow Requirement Graph",
1301
+ description: "Submit bounded requirement-graph candidates or inspect/validate the Core-owned canonical graph.",
1302
+ parameters: z.object({
1303
+ action: z.enum(["propose", "inspect", "validate", "context", "confirm"]),
1304
+ feature: z.string().min(1),
1305
+ expectedRevision: z.number().int().min(0).optional(),
1306
+ sliceId: z.string().min(1).optional(),
1307
+ nodeId: z.string().min(1).optional(),
1308
+ evidence: z.object({ cp_record: z.unknown().optional(), evidence_ref: z.unknown().optional() }).optional(),
1309
+ write: z.boolean().optional(),
1310
+ read: z.boolean().optional(),
1311
+ delta: z.object({
1312
+ op: z.enum(["add", "link", "update"]),
1313
+ kind: z.enum(["source", "code", "term", "object", "behavior", "requirement", "rule", "boundary", "slice", "risk", "decision", "acceptance", "api", "workload_shape", "workload_constraint"]).optional(),
1314
+ id: z.string().min(1).optional(),
1315
+ data: z.record(z.string(), z.unknown()).optional(),
1316
+ from: z.string().min(1).optional(),
1317
+ to: z.string().min(1).optional(),
1318
+ relation: z.string().min(1).optional(),
1319
+ propagation: z.enum(["one_hop", "full"]).optional(),
1320
+ }).optional(),
1321
+ }),
1322
+ approval: (args) => args.action === "propose" || args.action === "confirm" ? "write" : "read",
1323
+ async execute(_id, params, _signal, _onUpdate, ctx) {
1324
+ const args = [params.action, "--feature", params.feature, "--json"];
1325
+ if (params.action === "context") {
1326
+ if (!params.sliceId) return renderResult({ ok: false, exitCode: 2, stdout: "", stderr: "context requires sliceId" });
1327
+ args.push("--slice-id", params.sliceId);
1328
+ if (params.write) args.push("--write");
1329
+ if (params.read) args.push("--read");
1330
+ }
1331
+ if (params.action === "propose") {
1332
+ if (params.expectedRevision === undefined || !params.delta) {
1333
+ return renderResult({ ok: false, exitCode: 2, stdout: "", stderr: "propose requires expectedRevision and delta" });
1334
+ }
1335
+ args.push("--write", "--expected-revision", String(params.expectedRevision), "--delta-json", JSON.stringify(params.delta));
1336
+ if (activeContract?.allowedGraphDelta?.length) args.push("--allowed-kinds", activeContract.allowedGraphDelta.join(","));
1337
+ }
1338
+ if (params.action === "confirm") {
1339
+ if (params.expectedRevision === undefined || !params.nodeId || !params.evidence) {
1340
+ return renderResult({ ok: false, exitCode: 2, stdout: "", stderr: "confirm requires expectedRevision, nodeId and evidence" });
1341
+ }
1342
+ args.push("--write", "--expected-revision", String(params.expectedRevision), "--node-id", params.nodeId, "--evidence-json", JSON.stringify(params.evidence));
1343
+ const inspected = parseJsonResult(
1344
+ await runRuby(ctx.cwd, "workflow-orchestrator/scripts/requirement_graph.rb", ["inspect", "--feature", params.feature, "--json"]),
1345
+ );
1346
+ const node = Array.isArray(inspected?.graph?.nodes)
1347
+ ? inspected.graph.nodes.find((candidate: unknown) => typeof candidate === "object" && candidate !== null && (candidate as { id?: unknown }).id === params.nodeId) as { kind?: unknown; data?: unknown } | undefined
1348
+ : undefined;
1349
+ const architectureDecision = node?.kind === "decision" && typeof node?.data === "object" && node.data !== null && (node.data as { decision_type?: unknown }).decision_type === "architecture_option_comparison";
1350
+ const architectureRisk = node?.kind === "risk" && typeof node?.data === "object" && node.data !== null && (node.data as { risk_type?: unknown }).risk_type === "architecture_option_risk";
1351
+ if (node?.kind === "workload_shape" || node?.kind === "workload_constraint" || architectureDecision || architectureRisk) {
1352
+ const preview = JSON.stringify(node.data ?? {}, null, 2);
1353
+ const decisionDialog = architectureDecision;
1354
+ const riskDialog = architectureRisk;
1355
+ const confirmed = await ctx.ui.confirm(
1356
+ decisionDialog ? "确认架构方案取舍" : riskDialog ? "确认架构风险处理" : "确认工作负载事实或缺口",
1357
+ [
1358
+ `Feature:${params.feature}`,
1359
+ `节点:${params.nodeId}`,
1360
+ `类型:${decisionDialog ? "架构方案比较" : riskDialog ? "架构风险处理" : node.kind === "workload_shape" ? "工作负载形态" : "工作负载约束"}`,
1361
+ decisionDialog
1362
+ ? "确认的含义是:你已核对候选方案、证据与取舍,并接受其中 recommendation_option_id 作为本次架构选择。"
1363
+ : riskDialog
1364
+ ? "确认的含义是:你已核对失败模式、影响、处理方式、负责人和复核条件;它不代表生产风险从此消失。"
1365
+ : "确认的含义仅是:该事实或缺口、来源与责任归属已被你核对;它不代表容量已经满足,也不会替你选择实现方案。",
1366
+ "待确认内容:",
1367
+ preview.length > 8_000 ? `${preview.slice(0, 8_000)}\n…(内容过长,已截断)` : preview,
1368
+ ].join("\n\n"),
1369
+ );
1370
+ if (!confirmed) return renderResult({ ok: false, exitCode: 1, stdout: "", stderr: "User declined workload confirmation; node remains candidate." });
1371
+ args.push("--confirmed-interactively");
1372
+ }
1373
+ }
1374
+ return renderResult(
1375
+ await runRuby(ctx.cwd, "workflow-orchestrator/scripts/requirement_graph.rb", args),
1376
+ { action: params.action, feature: params.feature },
1377
+ );
1378
+ },
1379
+ });
1380
+
1381
+ pi.registerTool({
1382
+ name: "devflow_runtime_context",
1383
+ label: "Devflow Runtime Context",
1384
+ description: "Collect or inspect the P0 repository-declared dependency snapshot and its coverage.",
1385
+ parameters: z.object({
1386
+ action: z.enum(["collect", "inspect"]),
1387
+ }),
1388
+ approval: (args) => args.action === "collect" ? "write" : "read",
1389
+ async execute(_id, params, _signal, _onUpdate, ctx) {
1390
+ return renderResult(
1391
+ await runRuby(ctx.cwd, "workflow-orchestrator/scripts/runtime_context_collector.rb", [params.action, "--json"]),
1392
+ { action: params.action },
1393
+ );
1394
+ },
1395
+ });
1396
+
1397
+ pi.registerTool({
1398
+ name: "devflow_static_runtime_context",
1399
+ label: "Devflow Static Runtime Context",
1400
+ description: "Collect or inspect the P1 static evidence snapshot. It records only repository declarations and references, never production health or capacity.",
1401
+ parameters: z.object({
1402
+ action: z.enum(["collect", "inspect"]),
1403
+ }),
1404
+ approval: (args) => args.action === "collect" ? "write" : "read",
1405
+ async execute(_id, params, _signal, _onUpdate, ctx) {
1406
+ return renderResult(
1407
+ await runRuby(ctx.cwd, "workflow-orchestrator/scripts/static_runtime_context_collector.rb", [params.action, "--json"]),
1408
+ { action: params.action },
1409
+ );
1410
+ },
1411
+ });
1412
+
1413
+ pi.registerTool({
1414
+ name: "devflow_architecture_assessment",
1415
+ label: "Devflow Architecture Assessment",
1416
+ description: "Confirm an immutable delta set through an explicit user dialog, then assess only that Core-owned manifest against a pinned runtime-context snapshot.",
1417
+ parameters: z.object({
1418
+ action: z.enum(["inspect", "assess", "write-manifest"]),
1419
+ feature: z.string().min(1),
1420
+ baseSnapshotRevision: z.number().int().min(0).optional(),
1421
+ deltaSetId: z.string().min(1).optional(),
1422
+ deltaJson: z.array(z.record(z.string(), z.unknown())).optional(),
1423
+ sourceBundleRevision: z.string().min(1).optional(),
1424
+ expectedAssessmentRevision: z.number().int().min(0).optional(),
1425
+ write: z.boolean().optional(),
1426
+ }),
1427
+ approval: (args) => args.action === "write-manifest" || args.write ? "write" : "read",
1428
+ async execute(_id, params, _signal, _onUpdate, ctx) {
1429
+ const args = [params.action, "--feature", params.feature, "--json"];
1430
+ if (params.action === "inspect") {
1431
+ if (params.deltaSetId) args.push("--delta-set-id", params.deltaSetId);
1432
+ return renderResult(
1433
+ await runRuby(ctx.cwd, "workflow-orchestrator/scripts/architecture_assessment.rb", args),
1434
+ { action: params.action, feature: params.feature },
1435
+ );
1436
+ }
1437
+ if (!params.deltaSetId) {
1438
+ return renderResult({ ok: false, exitCode: 2, stdout: "", stderr: "deltaSetId is required" });
1439
+ }
1440
+ args.push("--delta-set-id", params.deltaSetId);
1441
+ if (params.action === "assess") {
1442
+ if (params.baseSnapshotRevision === undefined) {
1443
+ return renderResult({ ok: false, exitCode: 2, stdout: "", stderr: "assess requires baseSnapshotRevision and deltaSetId; deltas and manifest are Core-owned" });
1444
+ }
1445
+ args.push("--base-snapshot-revision", String(params.baseSnapshotRevision));
1446
+ if (params.write) {
1447
+ const inspected = parseJsonResult(await runRuby(ctx.cwd, "workflow-orchestrator/scripts/architecture_assessment.rb", ["inspect", "--feature", params.feature, "--delta-set-id", params.deltaSetId, "--json"]));
1448
+ const currentRevision = typeof inspected?.current_assessment_revision === "number" ? inspected.current_assessment_revision : undefined;
1449
+ if (currentRevision === undefined) {
1450
+ return renderResult({ ok: false, exitCode: 1, stdout: "", stderr: "Unable to read the current assessment revision for CAS." });
1451
+ }
1452
+ args.push("--write", "--expected-assessment-revision", String(currentRevision));
1453
+ }
1454
+ }
1455
+ if (params.action === "write-manifest") {
1456
+ if (!params.sourceBundleRevision || !params.deltaJson?.length) {
1457
+ return renderResult({ ok: false, exitCode: 2, stdout: "", stderr: "write-manifest requires deltaSetId, sourceBundleRevision and a non-empty deltaJson" });
1458
+ }
1459
+ const preview = params.deltaJson.map((rawDelta, index) => {
1460
+ const delta = rawDelta as Record<string, unknown>;
1461
+ const source = delta.source && typeof delta.source === "object" ? delta.source as Record<string, unknown> : {};
1462
+ return [
1463
+ `${index + 1}. ID:${String(delta.id ?? "(缺失)")}`,
1464
+ `依赖:${String(delta.subject ?? "(缺失)")}`,
1465
+ `来源:${String(source.locator ?? "(缺失)")}`,
1466
+ `来源 revision:${String(source.repository_revision ?? "(缺失)")}`,
1467
+ `内容哈希:${String(source.input_hash ?? "(缺失)")}`,
1468
+ ].join("\n");
1469
+ }).join("\n\n");
1470
+ const confirmed = await ctx.ui.confirm(
1471
+ "确认架构评估 Delta 集合",
1472
+ [
1473
+ `Feature:${params.feature}`,
1474
+ `Delta 集合:${params.deltaSetId}`,
1475
+ `需求/方案 revision:${params.sourceBundleRevision}`,
1476
+ "确认后 Core 会创建不可覆盖的 manifest;后续评估只能读取这一份确认内容。",
1477
+ "请核对以下依赖、来源与哈希:",
1478
+ preview,
1479
+ ].join("\n\n"),
1480
+ );
1481
+ if (!confirmed) return renderResult({ ok: false, exitCode: 1, stdout: "", stderr: "User declined delta-set confirmation; no manifest was written." });
1482
+ args.push("--source-bundle-revision", params.sourceBundleRevision, "--delta-json", JSON.stringify(params.deltaJson), "--confirmed-interactively", "--write");
1483
+ }
1484
+ return renderResult(
1485
+ await runRuby(ctx.cwd, "workflow-orchestrator/scripts/architecture_assessment.rb", args),
1486
+ { action: params.action, feature: params.feature },
1487
+ );
1488
+ },
1489
+ });
1490
+
1491
+ pi.registerTool({
1492
+ name: "devflow_workload_constraint",
1493
+ label: "Devflow Workload Constraint",
1494
+ description: "Validate a workload constraint (NFR) against its source-status schema.",
1495
+ parameters: z.object({
1496
+ constraintJson: z.record(z.string(), z.unknown()),
1497
+ }),
1498
+ approval: "read",
1499
+ async execute(_id, params, _signal, _onUpdate, ctx) {
1500
+ return renderResult(
1501
+ await runRuby(ctx.cwd, "workflow-orchestrator/scripts/workload_constraint.rb", ["validate", "--constraint-json", JSON.stringify(params.constraintJson), "--json"]),
1502
+ {},
1503
+ );
1504
+ },
1505
+ });
1506
+
1507
+ pi.registerTool({
1508
+ name: "devflow_architecture_constraint",
1509
+ label: "Devflow Architecture Constraint",
1510
+ description: "Validate candidate workload shape or workload constraint inputs without assigning confirmation, decisions, or gate status.",
1511
+ parameters: z.object({
1512
+ action: z.enum(["validate-shape", "validate-constraint"]),
1513
+ shapeJson: z.record(z.string(), z.unknown()).optional(),
1514
+ constraintJson: z.record(z.string(), z.unknown()).optional(),
1515
+ }),
1516
+ approval: "read",
1517
+ async execute(_id, params, _signal, _onUpdate, ctx) {
1518
+ if (params.action === "validate-shape" && !params.shapeJson) {
1519
+ return renderResult({ ok: false, exitCode: 2, stdout: "", stderr: "validate-shape requires shapeJson" });
1520
+ }
1521
+ if (params.action === "validate-constraint" && !params.constraintJson) {
1522
+ return renderResult({ ok: false, exitCode: 2, stdout: "", stderr: "validate-constraint requires constraintJson" });
1523
+ }
1524
+ const args = [params.action, "--json"];
1525
+ if (params.shapeJson) args.push("--shape-json", JSON.stringify(params.shapeJson));
1526
+ if (params.constraintJson) args.push("--constraint-json", JSON.stringify(params.constraintJson));
1527
+ return renderResult(
1528
+ await runRuby(ctx.cwd, "workflow-orchestrator/scripts/architecture_constraints.rb", args),
1529
+ { action: params.action },
1530
+ );
1531
+ },
1532
+ });
1533
+
1534
+ pi.registerTool({
1535
+ name: "devflow_architecture_feasibility",
1536
+ label: "Devflow Architecture Feasibility",
1537
+ description: "Read confirmed workload facts and a pinned static-context snapshot to derive an advisory architecture assessment. It never selects an option or claims production capacity.",
1538
+ parameters: z.object({
1539
+ feature: z.string().min(1),
1540
+ }),
1541
+ approval: "read",
1542
+ async execute(_id, params, _signal, _onUpdate, ctx) {
1543
+ return renderResult(
1544
+ await runRuby(ctx.cwd, "workflow-orchestrator/scripts/architecture_feasibility.rb", ["assess", "--feature", params.feature, "--json"]),
1545
+ { feature: params.feature },
1546
+ );
1547
+ },
1548
+ });
1549
+
1550
+ pi.registerTool({
1551
+ name: "devflow_architecture_brief",
1552
+ label: "Devflow Architecture Brief",
1553
+ description: "Inspect or regenerate the Core-derived architecture brief from the current graph and static-context snapshot. The brief is a view, never an agent-authored source of truth.",
1554
+ parameters: z.object({
1555
+ action: z.enum(["inspect", "render"]),
1556
+ feature: z.string().min(1),
1557
+ }),
1558
+ approval: (args) => args.action === "render" ? "write" : "read",
1559
+ async execute(_id, params, _signal, _onUpdate, ctx) {
1560
+ if (params.action === "inspect") {
1561
+ return renderResult(
1562
+ await runRuby(ctx.cwd, "workflow-orchestrator/scripts/architecture_brief.rb", ["inspect", "--feature", params.feature, "--json"]),
1563
+ { action: params.action, feature: params.feature },
1564
+ );
1565
+ }
1566
+ const inspected = parseJsonResult(
1567
+ await runRuby(ctx.cwd, "workflow-orchestrator/scripts/architecture_brief.rb", ["inspect", "--feature", params.feature, "--json"]),
1568
+ );
1569
+ const currentRevision = typeof inspected?.brief_revision === "number" ? inspected.brief_revision : undefined;
1570
+ if (currentRevision === undefined) {
1571
+ return renderResult({ ok: false, exitCode: 1, stdout: "", stderr: "Unable to read the current architecture brief revision for CAS." });
1572
+ }
1573
+ return renderResult(
1574
+ await runRuby(ctx.cwd, "workflow-orchestrator/scripts/architecture_brief.rb", ["render", "--feature", params.feature, "--write", "--expected-brief-revision", String(currentRevision), "--json"]),
1575
+ { action: params.action, feature: params.feature },
1576
+ );
1577
+ },
1578
+ });
1579
+
1580
+ pi.registerTool({
1581
+ name: "devflow_stale_check",
1582
+ label: "Devflow Stale Check",
1583
+ description: "Check field-level staleness of consumed evidence against the current snapshot.",
1584
+ parameters: z.object({
1585
+ consumedRevision: z.number().int().min(0),
1586
+ evidencePaths: z.array(z.string().min(1)),
1587
+ }),
1588
+ approval: "read",
1589
+ async execute(_id, params, _signal, _onUpdate, ctx) {
1590
+ return renderResult(
1591
+ await runRuby(ctx.cwd, "workflow-orchestrator/scripts/stale_check.rb", ["check", "--consumed-revision", String(params.consumedRevision), "--evidence-paths", JSON.stringify(params.evidencePaths), "--json"]),
1592
+ {},
1593
+ );
1594
+ },
1595
+ });
1596
+
1237
1597
  pi.registerTool({
1238
1598
  name: "devflow_evidence",
1239
1599
  label: "Devflow Evidence Pack",
@@ -1254,7 +1614,10 @@ export default function devflowCore(pi: ExtensionAPI) {
1254
1614
  if (!pack || typeof pack !== "object" || Array.isArray(pack)) return ["pack must be an object"];
1255
1615
  const record = pack as Record<string, unknown>;
1256
1616
  const errors: string[] = [];
1257
- if (record.schemaVersion !== 1) errors.push("schemaVersion must equal 1");
1617
+ const legacySchema = record.schemaVersion === 1;
1618
+ if (record.schemaVersion !== 2 && record.schemaVersion !== 1) errors.push("schemaVersion must equal 2 (or 1 during compatibility period)");
1619
+ if (record.origin !== "agent_claim") errors.push("origin must equal agent_claim");
1620
+ if (Object.prototype.hasOwnProperty.call(record, "passed")) errors.push("agent claims cannot declare passed");
1258
1621
  for (const key of ["facts", "unknowns", "risks"]) {
1259
1622
  if (!Array.isArray(record[key])) errors.push(`${key} must be an array`);
1260
1623
  }
@@ -1272,7 +1635,7 @@ export default function devflowCore(pi: ExtensionAPI) {
1272
1635
  return { content: [{ type: "text" as const, text: JSON.stringify(parsed, null, 2) }], details: { action: params.action, path: path.relative(ctx.cwd, destination), errors } };
1273
1636
  }
1274
1637
  if (!params.pack) return renderResult({ ok: false, exitCode: 2, stdout: "", stderr: "pack is required for create" }, { action: params.action });
1275
- const pack = { schemaVersion: 1, taskId, createdAt: new Date().toISOString(), ...params.pack };
1638
+ const pack = { schemaVersion: 2, taskId, createdAt: new Date().toISOString(), ...params.pack, origin: "agent_claim", evidenceClass: "non_gating_claim" };
1276
1639
  const errors = validatePack(pack);
1277
1640
  if (errors.length > 0) return renderResult({ ok: false, exitCode: 2, stdout: "", stderr: errors.join("; ") }, { action: params.action, errors });
1278
1641
  await mkdir(root, { recursive: true });
@@ -1284,26 +1647,19 @@ export default function devflowCore(pi: ExtensionAPI) {
1284
1647
  pi.registerTool({
1285
1648
  name: "devflow_contract",
1286
1649
  label: "Devflow Execution Contract",
1287
- description: "Show or activate a user-confirmed, path-bounded execution contract. Only an active contract may authorize low-cost implementation agents.",
1650
+ description: "Inspect or activate a Core-synthesized ExecutionContract v2. Paths, agents, checker IDs, and budgets are fixed by the packaged policy, never model input.",
1288
1651
  parameters: z.object({
1289
- action: z.enum(["status", "activate"]),
1290
- contractId: z.string().min(1).optional(),
1652
+ action: z.enum(["status", "inspect", "activate"]),
1291
1653
  feature: z.string().min(1).optional(),
1292
1654
  step: z.string().min(1).optional(),
1293
- allowedPaths: z.array(z.string().min(1)).default([]),
1294
- allowedAgents: z.array(z.enum(["implementation-worker", "verification-runner"])).default([]),
1295
- acceptance: z.array(z.string().min(1)).default([]),
1296
1655
  }),
1297
1656
  approval: "write",
1298
1657
  async execute(_id, params, _signal, _onUpdate, ctx) {
1299
1658
  const input = params as {
1300
- action: "status" | "activate";
1659
+ action: "status" | "inspect" | "activate";
1301
1660
  contractId?: string;
1302
1661
  feature?: string;
1303
1662
  step?: string;
1304
- allowedPaths: string[];
1305
- allowedAgents: string[];
1306
- acceptance: string[];
1307
1663
  };
1308
1664
  if (input.action === "status") {
1309
1665
  return {
@@ -1311,47 +1667,51 @@ export default function devflowCore(pi: ExtensionAPI) {
1311
1667
  details: { activeContract },
1312
1668
  };
1313
1669
  }
1314
- if (controlState.phase !== "planning") {
1315
- return renderResult({ ok: false, exitCode: 2, stdout: "", stderr: "Contracts may only be activated from the planning phase." });
1316
- }
1317
- if (!input.contractId || !input.feature || !input.step || input.allowedPaths.length === 0 || input.allowedAgents.length === 0 || input.acceptance.length === 0) {
1318
- return renderResult({ ok: false, exitCode: 2, stdout: "", stderr: "contractId, feature, step, allowedPaths, allowedAgents, and acceptance are required." });
1319
- }
1320
- let allowedPaths: string[];
1321
- try {
1322
- allowedPaths = input.allowedPaths.map(candidate => {
1323
- const relative = normalizedProjectPath(candidate, ctx.cwd);
1324
- if (!relative || relative.startsWith(".planning/")) throw new Error(`Invalid contract path: ${candidate}`);
1325
- return relative.replace(/\/$/, "");
1326
- });
1327
- } catch (error) {
1328
- return renderResult({ ok: false, exitCode: 2, stdout: "", stderr: error instanceof Error ? error.message : String(error) });
1329
- }
1670
+ if (!input.feature || !input.step) return renderResult({ ok: false, exitCode: 2, stdout: "", stderr: "feature and step are required; Core derives all control fields." });
1671
+ const inspected = await runRuby(ctx.cwd, "workflow-orchestrator/scripts/execution_contract.rb", ["inspect", "--feature", input.feature, "--step", input.step, "--json"]);
1672
+ const payload = parseJsonResult(inspected);
1673
+ const raw = payload?.contract;
1674
+ if (!controlPlaneSucceeded(inspected) || !raw || typeof raw !== "object") return renderResult(inspected, { action: input.action });
1675
+ const record = raw as Record<string, unknown>;
1676
+ const strings = (key: string): string[] => Array.isArray(record[key]) ? record[key].filter((item): item is string => typeof item === "string") : [];
1330
1677
  const contract: ActiveExecutionContract = {
1331
- contractId: input.contractId,
1332
- feature: input.feature,
1333
- step: input.step,
1334
- allowedPaths: [...new Set(allowedPaths)],
1335
- allowedAgents: [...new Set(input.allowedAgents)],
1336
- acceptance: [...new Set(input.acceptance)],
1337
- onUnknown: "block_and_escalate",
1678
+ contractId: String(record.contract_id ?? ""), feature: String(record.feature ?? ""), step: String(record.step ?? ""),
1679
+ stateRevision: typeof record.state_revision === "number" ? record.state_revision : undefined,
1680
+ graphRevision: typeof record.graph_revision === "number" ? record.graph_revision : undefined,
1681
+ allowedPaths: strings("allowed_paths"), allowedAgents: strings("allowed_agents"), allowedTools: strings("allowed_tools"), checkerIds: strings("checker_ids"),
1682
+ toolBudget: typeof record.tool_budget === "number" ? record.tool_budget : 0,
1683
+ frozenHash: String(record.frozen_hash ?? ""), acceptance: strings("acceptance"), allowedGraphDelta: strings("allowed_graph_delta"), onUnknown: "block_and_escalate",
1338
1684
  };
1685
+ if (!contract.contractId || !contract.frozenHash) return renderResult({ ok: false, exitCode: 1, stdout: "", stderr: "Core returned an invalid execution contract." });
1686
+ if (input.action === "inspect") return { content: [{ type: "text" as const, text: JSON.stringify(contract, null, 2) }], details: { contract } };
1687
+ if (controlState.phase !== "planning") return renderResult({ ok: false, exitCode: 2, stdout: "", stderr: "Contracts may only be activated from the planning phase." });
1339
1688
  const confirmed = await ctx.ui.confirm(
1340
1689
  "激活 Devflow 执行合同",
1341
1690
  [
1342
1691
  `合同:${contract.contractId}`,
1343
1692
  `Feature:${contract.feature}`,
1344
1693
  `步骤:${contract.step}`,
1694
+ `STATE revision:${contract.stateRevision ?? "未初始化"}`,
1695
+ `图谱 revision:${contract.graphRevision ?? 0}`,
1345
1696
  `可写路径:${contract.allowedPaths.join(", ")}`,
1346
1697
  `允许 Agent:${contract.allowedAgents.join(", ")}`,
1347
- `验收:${contract.acceptance.join(";")}`,
1698
+ `固定检查器:${contract.checkerIds.join(", ") || "无"}`,
1699
+ `冻结哈希:${contract.frozenHash}`,
1348
1700
  "确认后低成本模型只能在以上范围执行;未知项必须阻塞升级。",
1349
1701
  ].join("\n"),
1350
1702
  );
1351
1703
  if (!confirmed) return renderResult({ ok: false, exitCode: 1, stdout: "", stderr: "User declined execution contract activation." });
1352
1704
  try {
1353
- const destination = await persistContract(ctx.cwd, contract);
1705
+ const createArgs = ["create", "--feature", input.feature, "--step", input.step, "--write", "--json"];
1706
+ if (contract.stateRevision !== undefined) createArgs.push("--expected-state-revision", String(contract.stateRevision));
1707
+ if (contract.graphRevision !== undefined) createArgs.push("--expected-graph-revision", String(contract.graphRevision));
1708
+ const written = await runRuby(ctx.cwd, "workflow-orchestrator/scripts/execution_contract.rb", createArgs);
1709
+ if (!controlPlaneSucceeded(written)) return renderResult(written, { action: input.action });
1710
+ const writtenPayload = parseJsonResult(written);
1711
+ const destination = typeof writtenPayload?.path === "string" ? writtenPayload.path : ".planning/contracts/";
1712
+ contract.contractPath = destination;
1354
1713
  activeContract = contract;
1714
+ contractAgentCalls = 0;
1355
1715
  setControlState(ctx, executingState(`合同执行:${contract.contractId}`));
1356
1716
  return {
1357
1717
  content: [{ type: "text" as const, text: `Execution contract activated: ${destination}` }],
@@ -1483,6 +1843,17 @@ export default function devflowCore(pi: ExtensionAPI) {
1483
1843
  if (!params.step) {
1484
1844
  return renderResult({ ok: false, exitCode: 2, stdout: "", stderr: "step is required" });
1485
1845
  }
1846
+ const snapshot = parseJsonResult(await runRuby(ctx.cwd, "workflow-orchestrator/scripts/inspect_state.rb", ["--json"]));
1847
+ const feature = snapshot?.snapshot && typeof snapshot.snapshot === "object" ? (snapshot.snapshot as Record<string, unknown>).feature : undefined;
1848
+ if (typeof feature === "string" && feature) {
1849
+ const evidenceArgs = ["--feature", feature, "--step", params.step, "--write"];
1850
+ if (activeContract?.feature === feature && activeContract.step === params.step && activeContract.contractPath) evidenceArgs.push("--contract", activeContract.contractPath);
1851
+ const evidence = await runRuby(ctx.cwd, "workflow-orchestrator/scripts/machine_evidence.rb", evidenceArgs);
1852
+ const evidencePayload = parseJsonResult(evidence);
1853
+ if (!controlPlaneSucceeded(evidence) || evidencePayload?.complete !== true) {
1854
+ return renderResult({ ok: false, exitCode: 1, stdout: "", stderr: "machine evidence collection failed or required outputs are incomplete" }, { action: params.action, evidence: evidence.stdout });
1855
+ }
1856
+ }
1486
1857
  args.push("--complete-step", params.step);
1487
1858
  } else if (params.action === "confirm-checkpoint") {
1488
1859
  args.push("--confirm-checkpoint");