@guanlin4924/devflow-pi 0.1.8 → 0.1.10

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@guanlin4924/devflow-pi",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
4
4
  "type": "module",
5
5
  "description": "Devflow 专项研发 Pi,基于固定版本 OMP Runtime 组装研发状态机、代码智能、设计、实现与质量门禁。",
6
6
  "license": "UNLICENSED",
@@ -108,6 +108,7 @@ const DEVFLOW_CHINESE_LANGUAGE_POLICY = `
108
108
  Devflow 语言策略(强制):
109
109
  - 默认使用简体中文与用户沟通。
110
110
  - 需求、计划、Evidence Pack、Execution Contract、审查报告、验证报告和阻塞原因必须使用简体中文。
111
+ - 对用户可见的思考、推理摘要和阶段性进度必须使用简体中文;不要展示英文推理草稿来代替中文结论。
111
112
  - 文件名、类名、函数名、接口路径、命令、代码、日志原文和模型/Agent 名称保持原样,不翻译标识符。
112
113
  - 推理时先区分“事实、推断、未知、待用户确认”,不要用英文长篇解释替代证据。
113
114
  - 如果引用英文源码或工具输出,保留原文并紧跟中文解释。
@@ -1550,6 +1551,26 @@ export default function devflowCore(pi: ExtensionAPI) {
1550
1551
  ctx.ui.notify(`Devflow advance failed: ${inspected.result.stderr || inspected.result.stdout || "invalid inspector JSON"}`, "error");
1551
1552
  return;
1552
1553
  }
1554
+ if (inspected.snapshot.action === "workflow_complete" && inspected.snapshot.source_flow) {
1555
+ const revision = typeof inspected.snapshot.revision === "number" ? inspected.snapshot.revision : undefined;
1556
+ if (revision === undefined) {
1557
+ ctx.ui.notify("补救分支缺少 revision,拒绝返回主流程。", "error");
1558
+ return;
1559
+ }
1560
+ const result = await runRuby(ctx.cwd, "workflow-orchestrator/scripts/advance_state.rb", [
1561
+ "--write",
1562
+ "--json",
1563
+ "--expected-revision",
1564
+ String(revision),
1565
+ "--exit-failure-branch",
1566
+ ]);
1567
+ if (!result.ok) {
1568
+ ctx.ui.notify(`补救分支返回失败:${result.stderr || result.stdout}`, "error");
1569
+ return;
1570
+ }
1571
+ ctx.ui.notify("补救分支已返回主流程;仍需审查 reverify 结果,并在 CP-4 明确确认后才能继续。", "info");
1572
+ return;
1573
+ }
1553
1574
  if (inspected.snapshot.status === "waiting_checkpoint") {
1554
1575
  ctx.ui.notify("当前状态等待 checkpoint;请使用 /devflow checkpoint。", "warning");
1555
1576
  return;
@@ -16,6 +16,7 @@ options = {
16
16
  confirm_checkpoint: false,
17
17
  checkpoint_decision: nil,
18
18
  enter_failure_branch: false,
19
+ exit_failure_branch: false,
19
20
  expected_revision: nil,
20
21
  json: false
21
22
  }
@@ -28,6 +29,7 @@ OptionParser.new do |opts|
28
29
  opts.on("--confirm-checkpoint", "Advance from waiting checkpoint to the next pending step") { options[:confirm_checkpoint] = true }
29
30
  opts.on("--checkpoint-decision VALUE", "Set checkpoint decision, e.g. approved, rejected, needs_fix") { |value| options[:checkpoint_decision] = value }
30
31
  opts.on("--enter-failure-branch", "Transition into the configured failure branch for the current step") { options[:enter_failure_branch] = true }
32
+ opts.on("--exit-failure-branch", "Return from a completed failure branch to its configured source flow") { options[:exit_failure_branch] = true }
31
33
  opts.on("--expected-revision REVISION", Integer, "Reject a stale write unless STATE.md has this revision") { |value| options[:expected_revision] = value }
32
34
  opts.on("--json", "Print JSON output") { options[:json] = true }
33
35
  end.parse!
@@ -337,6 +339,46 @@ def fail_result(reason, details = {})
337
339
  { "ok" => false, "reason" => reason, "details" => details }
338
340
  end
339
341
 
342
+ # Return from a completed failure branch using its declared return_to contract.
343
+ # The caller must still explicitly confirm the returned checkpoint before any
344
+ # subsequent source-flow step can proceed.
345
+ def apply_return_to!(root, state, branch_workflow)
346
+ return_to = branch_workflow["return_to"]
347
+ source_flow = state["source_flow"]
348
+ source_workflow_config = state["source_workflow_config"]
349
+ return nil unless return_to && source_flow && source_workflow_config
350
+
351
+ main_workflow_path = File.join(root, source_workflow_config)
352
+ return nil unless File.exist?(main_workflow_path)
353
+
354
+ main_workflow = load_yaml_file(main_workflow_path)
355
+ main_steps = Array(main_workflow["steps"])
356
+ target_step_id = return_to["step"]
357
+ return nil unless main_steps.any? { |step| step.fetch("id") == target_step_id }
358
+
359
+ ordered_ids = main_steps.map { |step| step.fetch("id") }
360
+ idx = ordered_ids.index(target_step_id)
361
+ state["current_flow"] = return_to["flow"] || source_flow
362
+ state["workflow_config"] = source_workflow_config
363
+ state["current_step"] = target_step_id
364
+ state["status"] = return_to["status"] || "waiting_checkpoint"
365
+ state["completed_steps"] = ordered_ids[0..idx]
366
+ state["pending_steps"] = ordered_ids[(idx + 1)..-1] || []
367
+ state["last_completed_step"] = target_step_id
368
+ if return_to["checkpoint"]
369
+ state["checkpoint"] = return_to["checkpoint"]
370
+ state["checkpoint_label"] = checkpoint_label(main_workflow, return_to["checkpoint"])
371
+ else
372
+ state.delete("checkpoint")
373
+ state.delete("checkpoint_label")
374
+ end
375
+ state.delete("checkpoint_decision")
376
+ state.delete("source_flow")
377
+ state.delete("source_workflow_config")
378
+ "Failure branch completed. Returned to #{target_step_id} (#{state["current_flow"]})" \
379
+ "#{return_to["checkpoint"] ? ", waiting at #{return_to["checkpoint"]}" : ""}."
380
+ end
381
+
340
382
  project_lock = DevflowControlIO.acquire_project_lock(root) if options[:write]
341
383
 
342
384
  begin
@@ -413,11 +455,16 @@ result =
413
455
  next_step = step_by_id[next_id]
414
456
  action_description = "Step completed: #{step["label"]} (#{step_id}). Advanced to #{next_step ? next_step["label"] : next_id}."
415
457
  else
416
- state["current_step"] = step_id
417
- state["status"] = "completed"
418
- state.delete("checkpoint")
419
- state.delete("checkpoint_label")
420
- action_description = "Step completed: #{step["label"]} (#{step_id}). All steps complete."
458
+ return_description = state["source_flow"] ? apply_return_to!(root, state, workflow) : nil
459
+ if return_description
460
+ action_description = "Step completed: #{step["label"]} (#{step_id}). #{return_description}"
461
+ else
462
+ state["current_step"] = step_id
463
+ state["status"] = "completed"
464
+ state.delete("checkpoint")
465
+ state.delete("checkpoint_label")
466
+ action_description = "Step completed: #{step["label"]} (#{step_id}). All steps complete."
467
+ end
421
468
  end
422
469
 
423
470
  changed = true
@@ -483,10 +530,28 @@ result =
483
530
  end
484
531
  end
485
532
 
533
+ if result.nil? && options[:exit_failure_branch]
534
+ if state["source_flow"].nil? || state["source_workflow_config"].nil?
535
+ result = fail_result("not_in_failure_branch")
536
+ elsif state["status"] != "completed" || Array(state["pending_steps"]).any?
537
+ result = fail_result("failure_branch_incomplete", "status" => state["status"], "pending_steps" => state["pending_steps"])
538
+ else
539
+ return_description = apply_return_to!(root, state, workflow)
540
+ if return_description.nil?
541
+ result = fail_result("missing_return_to", "workflow" => state["current_flow"])
542
+ else
543
+ changed = true
544
+ actions << "failure_branch_exited"
545
+ action_description = return_description
546
+ end
547
+ end
548
+ end
549
+
486
550
  if result.nil?
487
551
  if changed
488
552
  state["state_updated_at"] = Date.today.to_s
489
553
  state["revision"] = current_revision + 1
554
+ workflow_path, workflow = load_workflow(root, state)
490
555
  end
491
556
  # Build the registry from the candidate state before validating it. A
492
557
  # step's output is normally written before this transition, so the
@@ -583,7 +583,11 @@ result =
583
583
  end
584
584
 
585
585
  if defined?(state) && state.is_a?(Hash) && result.is_a?(Hash)
586
- result = result.merge(revision: Integer(state.fetch("revision", 0)))
586
+ result = result.merge(
587
+ revision: Integer(state.fetch("revision", 0)),
588
+ source_flow: state["source_flow"],
589
+ source_workflow_config: state["source_workflow_config"]
590
+ )
587
591
  end
588
592
 
589
593
  if options[:json]
@@ -63,26 +63,31 @@ begin
63
63
  state = YAML.safe_load(front_matter(File.read(state_path)), permitted_classes: [], aliases: false)
64
64
  workflow_path = File.expand_path(state.fetch("workflow_config"), root)
65
65
  workflow = YAML.safe_load(File.read(workflow_path), permitted_classes: [], aliases: false)
66
- candidate = build_registry(root, workflow, state)
67
- validation = WorkflowStateValidator.validate(root: root, state_path: state_path, state_override: state, workflow_override: workflow, artifact_registry_override: candidate, strict_artifact_status: true, strict_registry_revision: true)
68
- current = Array(workflow["steps"]).find { |step| step["id"] == state["current_step"] }
69
- missing_current_outputs = Array(current && current["outputs"]).map do |output|
70
- next unless output["required"]
71
- path = interpolate_path(output.fetch("path"), state)
72
- path unless File.exist?(File.join(root, path))
73
- end.compact
74
- if missing_current_outputs.any?
75
- result = { "ok" => false, "classification" => "unsafe", "reason" => "missing_required_output", "missing_outputs" => missing_current_outputs, "plan" => { "state_transition" => "none", "registry_write" => false } }
76
- elsif !validation["ok"]
77
- result = { "ok" => false, "classification" => "unsafe", "reason" => "ambiguous_or_invalid_state", "issues" => validation["errors"], "plan" => { "state_transition" => "none", "registry_write" => false } }
78
- elsif !options[:confirm] || !options[:write]
79
- result = { "ok" => false, "classification" => "safe_repairable", "reason" => "confirmation_required", "revision" => state.fetch("revision", 0), "plan" => { "state_transition" => "none", "registry_write" => true, "registry" => candidate } }
80
- elsif options[:expected_revision] != state.fetch("revision", 0)
81
- result = { "ok" => false, "classification" => "unsafe", "reason" => "stale_revision", "expected_revision" => options[:expected_revision], "actual_revision" => state.fetch("revision", 0) }
66
+ registry_source = workflow.dig("artifact_registry", "source")
67
+ if !registry_source.is_a?(String) || registry_source.empty?
68
+ result = { "ok" => false, "classification" => "unsafe", "reason" => "missing_artifact_registry_config", "plan" => { "state_transition" => "none", "registry_write" => false } }
82
69
  else
83
- registry_path = File.join(root, workflow.dig("artifact_registry", "source"))
84
- DevflowControlIO.atomic_write(registry_path, candidate.to_yaml)
85
- result = { "ok" => true, "classification" => "safe_repairable", "written" => true, "revision" => state.fetch("revision", 0), "plan" => { "state_transition" => "none", "registry_write" => true } }
70
+ candidate = build_registry(root, workflow, state)
71
+ validation = WorkflowStateValidator.validate(root: root, state_path: state_path, state_override: state, workflow_override: workflow, artifact_registry_override: candidate, strict_artifact_status: true, strict_registry_revision: true)
72
+ current = Array(workflow["steps"]).find { |step| step["id"] == state["current_step"] }
73
+ missing_current_outputs = Array(current && current["outputs"]).map do |output|
74
+ next unless output["required"]
75
+ path = interpolate_path(output.fetch("path"), state)
76
+ path unless File.exist?(File.join(root, path))
77
+ end.compact
78
+ if missing_current_outputs.any?
79
+ result = { "ok" => false, "classification" => "unsafe", "reason" => "missing_required_output", "missing_outputs" => missing_current_outputs, "plan" => { "state_transition" => "none", "registry_write" => false } }
80
+ elsif !validation["ok"]
81
+ result = { "ok" => false, "classification" => "unsafe", "reason" => "ambiguous_or_invalid_state", "issues" => validation["errors"], "plan" => { "state_transition" => "none", "registry_write" => false } }
82
+ elsif !options[:confirm] || !options[:write]
83
+ result = { "ok" => false, "classification" => "safe_repairable", "reason" => "confirmation_required", "revision" => state.fetch("revision", 0), "plan" => { "state_transition" => "none", "registry_write" => true, "registry" => candidate } }
84
+ elsif options[:expected_revision] != state.fetch("revision", 0)
85
+ result = { "ok" => false, "classification" => "unsafe", "reason" => "stale_revision", "expected_revision" => options[:expected_revision], "actual_revision" => state.fetch("revision", 0) }
86
+ else
87
+ registry_path = File.join(root, registry_source)
88
+ DevflowControlIO.atomic_write(registry_path, candidate.to_yaml)
89
+ result = { "ok" => true, "classification" => "safe_repairable", "written" => true, "revision" => state.fetch("revision", 0), "plan" => { "state_transition" => "none", "registry_write" => true } }
90
+ end
86
91
  end
87
92
  end
88
93
  ensure
@@ -4,6 +4,18 @@ name: Verify Failure Remediation
4
4
  description: >
5
5
  Generic remediation flow for failed verification or rejected acceptance checkpoints.
6
6
 
7
+ artifact_registry:
8
+ source: ".planning/artifacts/index.yaml"
9
+ path_key: path
10
+ status_key: status
11
+ pending_statuses:
12
+ - planned
13
+ - pending_rerun
14
+ terminal_success_statuses:
15
+ - approved
16
+ - completed
17
+ - passed
18
+
7
19
  entry:
8
20
  from_flow: feature-development
9
21
  from_step: verify
@@ -10,11 +10,13 @@ tools:
10
10
  - edit
11
11
  - write
12
12
  model: "@task"
13
- thinkingLevel: high
13
+ thinkingLevel: medium
14
14
  autoloadSkills:
15
15
  - workflow-execute-phase
16
16
  ---
17
17
 
18
- Implement only the assigned plan slice. Read the relevant project rules, source, tests, and approved design artifacts before editing. Preserve unrelated work and report deviations instead of silently expanding scope.
18
+ Implement only the assigned plan slice. Do not re-plan the feature, compare alternatives, or explore unrelated modules. Read only the approved files, their immediate dependencies, and the focused tests needed for this slice. Preserve unrelated work and report deviations instead of silently expanding scope.
19
19
 
20
- Run focused validation and return changed files, commands, results, residual risks, and any blocked dependency. Never edit `.planning/STATE.md`, `.planning/artifacts/index.yaml`, workflow definitions, or checkpoint decisions; the parent session owns those through Devflow tools.
20
+ If the execution contract is missing, ambiguous, or contradicted by the code, stop before editing and return `STATUS: escalate` with the conflicting evidence. Run the smallest focused validation once; report failures rather than repeatedly trying speculative fixes.
21
+
22
+ Return `STATUS`, changed files, validation evidence, and one escalation/blocker if needed. Never edit `.planning/STATE.md`, `.planning/artifacts/index.yaml`, workflow definitions, or checkpoint decisions; the parent session owns those through Devflow tools.
@@ -1,27 +1,28 @@
1
1
  ---
2
2
  name: project-scout
3
- description: Read-only project explorer that locates implementation evidence, architecture boundaries, and affected files without proposing speculative changes.
3
+ description: Low-cost read-only evidence collector for bounded code-location questions; escalates when the question needs architecture or product judgment.
4
4
  tools:
5
5
  - read
6
6
  - grep
7
7
  - find
8
- - lsp
9
8
  - devflow_code_index
10
9
  model: "@smol"
11
- thinkingLevel: medium
10
+ thinkingLevel: low
12
11
  autoloadSkills:
13
12
  - code-index-query
14
- - workflow-code-index
15
13
  ---
16
14
 
17
- Act as a read-only evidence collector. Start from project indexes when they are fresh, then verify every material claim against LSP, search results, and source code. An index is an accelerator, never an authority.
15
+ Act as a bounded read-only evidence collector, not a planner. Use the code index first, then make at most three targeted reads or searches needed to answer the assigned question. Do not inspect adjacent systems merely to be thorough.
18
16
 
19
- Return:
17
+ If the answer needs architecture tradeoffs, requirements interpretation, a cross-module decision, or evidence remains insufficient after the bounded search, stop and return `STATUS: escalate`. Do not guess or continue exploring.
20
18
 
21
- 1. relevant entry points and symbols with paths;
22
- 2. cross-layer call paths;
23
- 3. conventions and tests that constrain the change;
24
- 4. unknowns or stale evidence;
25
- 5. the smallest defensible affected-file set.
19
+ Return exactly:
20
+
21
+ ```text
22
+ STATUS: done | escalate | blocked
23
+ EVIDENCE: up to 5 path/symbol facts
24
+ ANSWER: concise answer or smallest affected-file set
25
+ NEXT: only the required escalation or blocker; otherwise none
26
+ ```
26
27
 
27
28
  Do not edit files, mutate workflow state, or widen the task beyond the supplied question.
package/src/runtime.ts CHANGED
@@ -35,6 +35,12 @@ const MANAGED_SETTINGS: Record<string, unknown> = {
35
35
  "task.maxConcurrency": 1,
36
36
  "task.maxRecursionDepth": 1,
37
37
  "task.enableLsp": true,
38
+ // Keep cheap workers bounded by role prompts. OMP's loop guards remain
39
+ // enabled, while these switches prevent accidental high-effort keywords from
40
+ // widening a normal Devflow turn into an unbounded orchestration request.
41
+ "magicKeywords.ultrathink": false,
42
+ "magicKeywords.orchestrate": false,
43
+ "magicKeywords.workflow": false,
38
44
  "task.agentModelOverrides": {
39
45
  "project-scout": "@smol",
40
46
  "requirements-auditor": "@slow",