@guanlin4924/devflow-pi 0.1.105 → 0.1.107
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/README.md +10 -8
- package/package.json +1 -1
- package/plugins/devflow-core/extensions/devflow-core.ts +287 -394
- package/plugins/devflow-core/runtime/hooks/context_sync_gate.py +3 -1
- package/plugins/devflow-core/runtime/hooks/hook_common.py +6 -5
- package/plugins/devflow-core/runtime/hooks/index_gate.py +19 -26
- package/plugins/devflow-core/skills/wireframe-proposal/SKILL.md +2 -0
- package/plugins/devflow-core/skills/wireframe-render/SKILL.md +2 -0
- package/plugins/devflow-core/skills/wireframe-review/SKILL.md +2 -0
- package/plugins/devflow-core/skills/wireframe-validate/SKILL.md +2 -0
- package/plugins/devflow-core/skills/workflow-dynamic/SKILL.md +41 -0
- package/plugins/devflow-core/skills/workflow-orchestrator/SKILL.md +37 -50
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/advance_state.rb +46 -191
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/artifact_specs.rb +12 -0
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/dynamic_plan.rb +504 -0
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/execution_contract.rb +38 -1
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/gate_policy.rb +11 -0
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/gate_result.rb +12 -0
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/gate_runtime.rb +4 -9
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/inspect_state.rb +19 -86
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/parallel_recover.rb +24 -13
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/repair_control_plane.rb +4 -4
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/sync_artifacts.rb +7 -1
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/validate_state.rb +17 -39
- package/plugins/devflow-core/skills/workflow-orchestrator/scripts/wireframe_commit.rb +39 -3
- package/plugins/devflow-core/skills/workflow-orchestrator/templates/feature-development.yaml +2 -3
- package/plugins/devflow-core/skills/workflow-orchestrator/templates/regression-acceptance.yaml +0 -1
- package/plugins/devflow-core/skills/workflow-pipeline/SKILL.md +13 -5
- package/plugins/devflow-core/skills/workflow-pipeline/references/pipeline-flow.md +1 -1
- package/plugins/devflow-core/skills/workflow-project-context/SKILL.md +2 -0
- package/plugins/devflow-design/skills/workflow-api-call-guide/SKILL.md +2 -0
- package/plugins/devflow-design/skills/workflow-api-contract/SKILL.md +3 -1
- package/plugins/devflow-design/skills/workflow-api-supplement/SKILL.md +2 -0
- package/plugins/devflow-design/skills/workflow-architecture-feasibility/SKILL.md +2 -0
- package/plugins/devflow-design/skills/workflow-backend-tech-spec/SKILL.md +6 -4
- package/plugins/devflow-design/skills/workflow-design-rule-pack/SKILL.md +2 -0
- package/plugins/devflow-design/skills/workflow-feature-breakdown/SKILL.md +2 -0
- package/plugins/devflow-design/skills/workflow-frontend-tech-spec/SKILL.md +6 -4
- package/plugins/devflow-design/skills/workflow-integration-verify/SKILL.md +3 -1
- package/plugins/devflow-design/skills/workflow-ui-spec/SKILL.md +4 -2
- package/plugins/devflow-implementation/agents/implementation-worker.md +2 -8
- package/plugins/devflow-implementation/skills/workflow-bug-fix/SKILL.md +5 -0
- package/plugins/devflow-implementation/skills/workflow-change-plan/SKILL.md +3 -1
- package/plugins/devflow-implementation/skills/workflow-execute-phase/SKILL.md +6 -1
- package/plugins/devflow-intel/skills/code-index-query/SKILL.md +2 -0
- package/plugins/devflow-intel/skills/workflow-code-index/SKILL.md +2 -0
- package/plugins/devflow-intel/skills/workflow-context-sync/SKILL.md +2 -0
- package/plugins/devflow-intel/skills/workflow-update-navigation/SKILL.md +3 -1
- package/plugins/devflow-quality/agents/code-reviewer.md +2 -0
- package/plugins/devflow-quality/skills/code-review/SKILL.md +7 -2
- package/plugins/devflow-quality/skills/workflow-acceptance-test-design/SKILL.md +3 -1
- package/plugins/devflow-quality/skills/workflow-gen-test-guide/SKILL.md +2 -0
- package/plugins/devflow-quality/skills/workflow-generate-mainflow-cases/SKILL.md +2 -0
- package/plugins/devflow-quality/skills/workflow-human-acceptance/SKILL.md +2 -0
- package/plugins/devflow-quality/skills/workflow-regression-test-design/SKILL.md +2 -0
- package/plugins/devflow-quality/skills/workflow-release-closeout/SKILL.md +7 -5
- package/plugins/devflow-quality/skills/workflow-run-acceptance-tests/SKILL.md +11 -6
- package/plugins/devflow-requirements/skills/requirement-feasibility-review/SKILL.md +2 -0
- package/plugins/devflow-requirements/skills/workflow-demand-clarify/SKILL.md +2 -0
- package/plugins/devflow-requirements/skills/workflow-domain-modeling/SKILL.md +2 -0
- package/plugins/devflow-requirements/skills/workflow-requirement-baseline/SKILL.md +6 -2
- package/plugins/devflow-requirements/skills/workflow-requirement-triage/SKILL.md +5 -0
- package/plugins/devflow-requirements/skills/workflow-wayfinder/SKILL.md +2 -0
- package/src/constants.ts +1 -1
- package/src/dynamic-workflow.ts +31 -0
- package/templates/omp/AGENTS.md +8 -3
|
@@ -6,6 +6,8 @@ require "optparse"
|
|
|
6
6
|
require "yaml"
|
|
7
7
|
require_relative "validate_state"
|
|
8
8
|
require_relative "checkpoint_meta"
|
|
9
|
+
require_relative "gate_policy"
|
|
10
|
+
require_relative "dynamic_plan"
|
|
9
11
|
|
|
10
12
|
options = { json: false, state: nil }
|
|
11
13
|
OptionParser.new do |opts|
|
|
@@ -15,6 +17,11 @@ OptionParser.new do |opts|
|
|
|
15
17
|
end.parse!
|
|
16
18
|
|
|
17
19
|
root = Dir.pwd
|
|
20
|
+
dynamic_state = DynamicPlan.read_state(root)
|
|
21
|
+
if dynamic_state && dynamic_state["current_flow"] == "dynamic"
|
|
22
|
+
puts JSON.pretty_generate(DynamicPlan.snapshot(root, dynamic_state))
|
|
23
|
+
exit 0
|
|
24
|
+
end
|
|
18
25
|
state_path = options[:state] ? File.expand_path(options[:state], root) : File.join(root, ".devflow/planning", "STATE.md")
|
|
19
26
|
|
|
20
27
|
def fail_result(reason, details = {})
|
|
@@ -192,19 +199,7 @@ def track_allowed_steps(track)
|
|
|
192
199
|
end
|
|
193
200
|
|
|
194
201
|
def next_pending_step_id(state, pending_ids, step_by_id, completed_ids)
|
|
195
|
-
|
|
196
|
-
unless track
|
|
197
|
-
return pending_ids.find do |step_id|
|
|
198
|
-
step_by_id[step_id] && dependencies_satisfied?(step_by_id[step_id], completed_ids)
|
|
199
|
-
end
|
|
200
|
-
end
|
|
201
|
-
|
|
202
|
-
allowed = track_allowed_steps(track)
|
|
203
|
-
pending_ids.find do |step_id|
|
|
204
|
-
allowed.include?(step_id) &&
|
|
205
|
-
step_by_id[step_id] &&
|
|
206
|
-
dependencies_satisfied?(step_by_id[step_id], completed_ids)
|
|
207
|
-
end
|
|
202
|
+
pending_ids.find { |id| step_by_id[id] && dependencies_satisfied?(step_by_id[id], completed_ids) } || pending_ids.find { |id| step_by_id[id] }
|
|
208
203
|
end
|
|
209
204
|
|
|
210
205
|
def track_boundary(state, pending_ids, completed_ids)
|
|
@@ -366,27 +361,12 @@ result =
|
|
|
366
361
|
|
|
367
362
|
if missing.any?
|
|
368
363
|
fail_result("missing_state_fields", fields: missing)
|
|
369
|
-
elsif project_context_missing?(root)
|
|
370
|
-
project_context_result(
|
|
371
|
-
"incomplete_project_context",
|
|
372
|
-
missing: [
|
|
373
|
-
".devflow/planning/PROJECT.md",
|
|
374
|
-
".devflow/planning/navigation/INDEX.md",
|
|
375
|
-
".devflow/context/spec/index.md"
|
|
376
|
-
]
|
|
377
|
-
)
|
|
378
364
|
else
|
|
379
365
|
pending_template_blockers = Array(state["blockers"]).select do |blocker|
|
|
380
366
|
blocker.is_a?(Hash) && blocker["type"] == "PENDING_TEMPLATE"
|
|
381
367
|
end
|
|
382
368
|
|
|
383
|
-
|
|
384
|
-
fail_result(
|
|
385
|
-
"pending_depth_spec_templates",
|
|
386
|
-
blockers: pending_template_blockers,
|
|
387
|
-
recovery: "add the missing template or mark the topic NOT_APPLICABLE, then rerun workflow-project-context"
|
|
388
|
-
)
|
|
389
|
-
else
|
|
369
|
+
begin
|
|
390
370
|
workflow_path = state["workflow_config"] ||
|
|
391
371
|
File.join(".devflow/planning", "workflows", "#{state["current_flow"]}.yaml")
|
|
392
372
|
abs_workflow_path = File.join(root, workflow_path)
|
|
@@ -413,6 +393,8 @@ result =
|
|
|
413
393
|
strict_artifact_status: true
|
|
414
394
|
)
|
|
415
395
|
|
|
396
|
+
validation["warnings"] << GatePolicy.advisory({ "code" => "incomplete_project_context", "message" => "project context indexes are missing" }) if project_context_missing?(root)
|
|
397
|
+
validation["warnings"].concat(pending_template_blockers.map { |entry| GatePolicy.advisory(entry) })
|
|
416
398
|
if !validation["ok"]
|
|
417
399
|
{
|
|
418
400
|
current_flow: state["current_flow"],
|
|
@@ -429,6 +411,8 @@ result =
|
|
|
429
411
|
},
|
|
430
412
|
next_step: nil,
|
|
431
413
|
workflow_config: workflow_path,
|
|
414
|
+
warnings: Array(state["gate_advisories"]) + Array(validation["warnings"]),
|
|
415
|
+
gate_mode: "soft",
|
|
432
416
|
artifact_registry: artifact_registry_summary(root, artifact_by_path).merge(
|
|
433
417
|
"path" => artifact_registry_path,
|
|
434
418
|
"error" => artifact_registry_error,
|
|
@@ -446,41 +430,6 @@ result =
|
|
|
446
430
|
},
|
|
447
431
|
recovery: "Fix missing/invalid artifacts or call devflow_artifact with action=sync and preserve=false, then call devflow_state with action=inspect."
|
|
448
432
|
}
|
|
449
|
-
elsif (drift = validation["warnings"].find { |issue| issue["code"] == "current_output_registry_drift" })
|
|
450
|
-
{
|
|
451
|
-
current_flow: state["current_flow"],
|
|
452
|
-
current_feature: state["current_feature"],
|
|
453
|
-
current_phase: state["current_phase"],
|
|
454
|
-
status: state["status"],
|
|
455
|
-
completed_steps: completed_ids,
|
|
456
|
-
pending_steps: pending_ids,
|
|
457
|
-
current_step: current_step && {
|
|
458
|
-
id: current_step["id"],
|
|
459
|
-
label: current_step["label"],
|
|
460
|
-
skill: current_step["skill"],
|
|
461
|
-
action: current_step["action"]
|
|
462
|
-
},
|
|
463
|
-
next_step: nil,
|
|
464
|
-
workflow_config: workflow_path,
|
|
465
|
-
artifact_registry: artifact_registry_summary(root, artifact_by_path).merge(
|
|
466
|
-
"path" => artifact_registry_path,
|
|
467
|
-
"error" => artifact_registry_error,
|
|
468
|
-
"current_outputs_registered" => 0
|
|
469
|
-
),
|
|
470
|
-
action: "recover_artifact_registry",
|
|
471
|
-
reason: "current_output_registry_drift",
|
|
472
|
-
drift: drift,
|
|
473
|
-
recovery_plan: {
|
|
474
|
-
classification: "safe_repairable",
|
|
475
|
-
state_transition: "none",
|
|
476
|
-
registry_write: true,
|
|
477
|
-
confirmation_required: true,
|
|
478
|
-
expected_revision: state.fetch("revision", 0),
|
|
479
|
-
diff: [{ path: drift["path"], registry_exists: drift["registry_exists"], filesystem_exists: true,
|
|
480
|
-
registry_content_valid: drift["registry_content_valid"], filesystem_content_valid: true }]
|
|
481
|
-
},
|
|
482
|
-
recovery: "After reviewing this diff, call devflow_recovery(expectedRevision=#{state.fetch("revision", 0)}, confirm=true). It only reconciles registry metadata; explicitly complete the step separately when ready."
|
|
483
|
-
}
|
|
484
433
|
elsif current_step.nil?
|
|
485
434
|
fallback_next_id = next_pending_step_id(state, pending_ids, step_by_id, completed_ids)
|
|
486
435
|
fallback_next_step = fallback_next_id && step_by_id[fallback_next_id]
|
|
@@ -604,6 +553,8 @@ result =
|
|
|
604
553
|
},
|
|
605
554
|
parallel_window: parallel_window,
|
|
606
555
|
workflow_config: workflow_path,
|
|
556
|
+
warnings: Array(state["gate_advisories"]) + Array(validation["warnings"]),
|
|
557
|
+
gate_mode: "soft",
|
|
607
558
|
artifact_registry: artifact_registry_summary(root, artifact_by_path).merge(
|
|
608
559
|
"path" => artifact_registry_path,
|
|
609
560
|
"error" => artifact_registry_error,
|
|
@@ -614,26 +565,7 @@ result =
|
|
|
614
565
|
|
|
615
566
|
boundary = track_boundary(state, pending_ids, completed_ids)
|
|
616
567
|
|
|
617
|
-
if
|
|
618
|
-
base.merge(
|
|
619
|
-
action: "blocked",
|
|
620
|
-
reason: artifact_registry_error,
|
|
621
|
-
recovery: "Call devflow_artifact with action=sync."
|
|
622
|
-
)
|
|
623
|
-
elsif boundary
|
|
624
|
-
base.merge(
|
|
625
|
-
action: "blocked",
|
|
626
|
-
reason: boundary["reason"],
|
|
627
|
-
message: boundary["message"],
|
|
628
|
-
active_track: active_track(state)
|
|
629
|
-
)
|
|
630
|
-
elsif failure_branch
|
|
631
|
-
base.merge(
|
|
632
|
-
action: "route_failure_branch",
|
|
633
|
-
reason: "failure_branch_triggered",
|
|
634
|
-
failure_branch: failure_branch
|
|
635
|
-
)
|
|
636
|
-
elsif state["status"] == "waiting_checkpoint"
|
|
568
|
+
if state["status"] == "waiting_checkpoint"
|
|
637
569
|
missing_outputs = current_outputs
|
|
638
570
|
.select { |output| output["required"] && !output["exists"] }
|
|
639
571
|
.map { |output| output["resolved_path"] }
|
|
@@ -647,8 +579,9 @@ result =
|
|
|
647
579
|
end
|
|
648
580
|
missing_outputs.concat(checkpoint_meta_errors.map { |entry| entry["path"] }.compact)
|
|
649
581
|
base.merge(
|
|
650
|
-
action:
|
|
651
|
-
reason:
|
|
582
|
+
action: "await_user_confirmation",
|
|
583
|
+
reason: nil,
|
|
584
|
+
warnings: Array(base[:warnings]) + (missing_outputs.empty? ? [] : [{ code: "missing_checkpoint_outputs", paths: missing_outputs, soft_gate: true }]),
|
|
652
585
|
missing_outputs: missing_outputs,
|
|
653
586
|
checkpoint_meta_errors: checkpoint_meta_errors,
|
|
654
587
|
resume_behavior: checkpoint && checkpoint["resume_behavior"],
|
|
@@ -15,6 +15,8 @@ require "optparse"
|
|
|
15
15
|
require "time"
|
|
16
16
|
require "yaml"
|
|
17
17
|
require_relative "control_io"
|
|
18
|
+
require_relative "artifact_specs"
|
|
19
|
+
require_relative "gate_policy"
|
|
18
20
|
require_relative "parallel_run"
|
|
19
21
|
|
|
20
22
|
options = {
|
|
@@ -54,9 +56,8 @@ def interpolate_path(path, state)
|
|
|
54
56
|
end
|
|
55
57
|
|
|
56
58
|
def required_output_specs(step)
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
outputs
|
|
59
|
+
ArtifactSpecs.for_step(step).select { |item| item["required"] == true }
|
|
60
|
+
|
|
60
61
|
end
|
|
61
62
|
|
|
62
63
|
def actual_content_issues(path, checks)
|
|
@@ -143,12 +144,13 @@ def generate_machine_evidence(root, feature, state_revision, branch, expected_pa
|
|
|
143
144
|
packed_paths = Array(pack["files"]).select { |item| item.is_a?(Hash) && item["required"] == true }.map { |item| item["path"].to_s }.sort
|
|
144
145
|
errors << "machine evidence required outputs mismatch" unless packed_paths == expected_paths.sort
|
|
145
146
|
errors << "machine evidence contains a missing required file" unless Array(pack["files"]).all? { |item| item.is_a?(Hash) && item["exists"] == true && item["sha256"].to_s.match?(/\A[0-9a-f]{64}\z/) }
|
|
146
|
-
|
|
147
|
+
recovery_check(errors.empty?, errors.join("; "))
|
|
147
148
|
|
|
148
149
|
output_fingerprint = Digest::SHA256.hexdigest(
|
|
149
150
|
Array(pack["files"]).map { |item| [item["path"].to_s, item["sha256"].to_s] }.sort.to_json,
|
|
150
151
|
)
|
|
151
152
|
{
|
|
153
|
+
"valid" => errors.empty?,
|
|
152
154
|
"evidence_path" => evidence_path,
|
|
153
155
|
"evidence_sha256" => Digest::SHA256.file(absolute).hexdigest,
|
|
154
156
|
"output_fingerprint" => output_fingerprint,
|
|
@@ -213,6 +215,13 @@ rescue JSON::ParserError => error
|
|
|
213
215
|
{ "ok" => false, "reason" => "join_returned_invalid_json", "message" => error.message, "stderr" => stderr.strip, "exit_status" => status.exitstatus }
|
|
214
216
|
end
|
|
215
217
|
|
|
218
|
+
def recovery_check(condition, message)
|
|
219
|
+
return if condition
|
|
220
|
+
|
|
221
|
+
$recovery_advisories << GatePolicy.advisory({ "code" => "parallel_recovery_check_failed", "message" => message })
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
$recovery_advisories = []
|
|
216
225
|
root = Dir.pwd
|
|
217
226
|
feature = options[:feature].to_s
|
|
218
227
|
result = nil
|
|
@@ -244,7 +253,7 @@ begin
|
|
|
244
253
|
raise ArgumentError, "parallel run not found" unless run
|
|
245
254
|
recovery = recovery_id(run, options[:expected_state_revision], options[:expected_run_revision])
|
|
246
255
|
branches = Array(run["branches"])
|
|
247
|
-
already_joined = state["parallel_run_ref"].nil? && run["status"] == "completed" && branches.any? && branches.all? { |branch| branch["status"] == "succeeded" }
|
|
256
|
+
already_joined = state["parallel_run_ref"].nil? && run["status"] == "completed" && branches.any? && (branches.all? { |branch| branch["status"] == "succeeded" } || run["completion_with_warnings"])
|
|
248
257
|
if already_joined
|
|
249
258
|
append_audit(root, feature, {
|
|
250
259
|
"schema_version" => 1, "origin" => "core.parallel_recovery/v1", "event" => "idempotent_noop",
|
|
@@ -264,7 +273,7 @@ begin
|
|
|
264
273
|
raise ArgumentError, "parallel run is bound to stale STATE revision" unless Integer(run.fetch("bound_state_revision")) == state_revision
|
|
265
274
|
raise ArgumentError, "parallel run has no branches" if branches.empty?
|
|
266
275
|
ineligible = branches.select { |branch| %w[failed aborted].include?(branch["status"].to_s) }
|
|
267
|
-
|
|
276
|
+
recovery_check(ineligible.empty?, "failed/aborted branches were not retried: #{ineligible.map { |branch| branch['branch_id'] }.join(', ')}")
|
|
268
277
|
previous_validation = prior_validation(root, feature, recovery, run)
|
|
269
278
|
unless current_run_revision == options[:expected_run_revision] || previous_validation
|
|
270
279
|
raise ArgumentError, "stale parallel run revision: expected=#{options[:expected_run_revision]}, actual=#{current_run_revision}"
|
|
@@ -275,9 +284,10 @@ begin
|
|
|
275
284
|
registry_source = workflow.dig("artifact_registry", "source").to_s
|
|
276
285
|
raise ArgumentError, "workflow artifact_registry.source is required for recovery" if registry_source.empty?
|
|
277
286
|
registry_path, = DevflowControlIO.resolve_control_path(root, registry_source, :registry)
|
|
278
|
-
registry = load_yaml_mapping(registry_path, "artifact registry")
|
|
279
|
-
|
|
280
|
-
|
|
287
|
+
registry = File.file?(registry_path) ? load_yaml_mapping(registry_path, "artifact registry") : {}
|
|
288
|
+
recovery_check(!registry.empty?, "artifact registry missing")
|
|
289
|
+
recovery_check(registry["state_revision"] == state_revision, "artifact registry revision mismatch")
|
|
290
|
+
recovery_check(registry.dig("registry_scope", "feature").to_s == feature, "artifact registry feature mismatch")
|
|
281
291
|
registry_by_path = Array(registry["artifacts"]).each_with_object({}) do |artifact, index|
|
|
282
292
|
raise ArgumentError, "artifact registry entry must be a mapping" unless artifact.is_a?(Hash)
|
|
283
293
|
path = artifact["path"].to_s
|
|
@@ -292,7 +302,8 @@ begin
|
|
|
292
302
|
raise ArgumentError, "parallel branch workflow step is unknown: #{branch['workflow_step']}" unless step
|
|
293
303
|
artifacts = validate_registered_outputs(root, workflow, state, step, registry_by_path)
|
|
294
304
|
invalid = artifacts.reject { |artifact| artifact["valid"] }
|
|
295
|
-
|
|
305
|
+
recovery_check(invalid.empty?, "branch #{branch['branch_id']} artifact validation failed: #{invalid.to_json}")
|
|
306
|
+
next unless invalid.empty?
|
|
296
307
|
machine = generate_machine_evidence(root, feature, state_revision, branch, artifacts.map { |artifact| artifact["path"] })
|
|
297
308
|
validation_records << {
|
|
298
309
|
"branch_id" => branch["branch_id"], "workflow_step" => branch["workflow_step"],
|
|
@@ -310,6 +321,7 @@ begin
|
|
|
310
321
|
|
|
311
322
|
validation_records.each do |validated|
|
|
312
323
|
machine = validated.fetch("machine_evidence")
|
|
324
|
+
next unless machine["valid"]
|
|
313
325
|
updated = ParallelRun.update_branch(
|
|
314
326
|
root: root, feature: feature, branch_id: validated.fetch("branch_id"),
|
|
315
327
|
expected_revision: current_run_revision, status: "succeeded",
|
|
@@ -320,9 +332,7 @@ begin
|
|
|
320
332
|
recovered_branches << validated.fetch("branch_id")
|
|
321
333
|
end
|
|
322
334
|
final_run = ParallelRun.load(root, feature)
|
|
323
|
-
|
|
324
|
-
raise ArgumentError, "recovery did not produce an all-succeeded run"
|
|
325
|
-
end
|
|
335
|
+
recovery_check(Array(final_run["branches"]).all? { |branch| branch["status"] == "succeeded" }, "recovery did not produce an all-succeeded run; branch statuses preserved")
|
|
326
336
|
append_audit(root, feature, {
|
|
327
337
|
"schema_version" => 1, "origin" => "core.parallel_recovery/v1", "event" => "branches_recovered",
|
|
328
338
|
"recovery_id" => recovery, "reason" => options[:reason], "run_id" => final_run["run_id"],
|
|
@@ -382,5 +392,6 @@ rescue StandardError => error
|
|
|
382
392
|
result = { "ok" => false, "reason" => "parallel_recovery_refused", "message" => error.message, "recovery_id" => recovery }
|
|
383
393
|
end
|
|
384
394
|
|
|
395
|
+
result["warnings"] = $recovery_advisories + Array(result.dig("details", "advisories")) unless $recovery_advisories.empty?
|
|
385
396
|
puts(options[:json] ? JSON.generate(result) : JSON.pretty_generate(result))
|
|
386
397
|
exit exit_status
|
|
@@ -6,6 +6,8 @@ require "json"
|
|
|
6
6
|
require "optparse"
|
|
7
7
|
require "yaml"
|
|
8
8
|
require_relative "control_io"
|
|
9
|
+
require_relative "artifact_specs"
|
|
10
|
+
require_relative "gate_policy"
|
|
9
11
|
require_relative "validate_state"
|
|
10
12
|
require_relative "phase_ownership"
|
|
11
13
|
|
|
@@ -30,7 +32,7 @@ end
|
|
|
30
32
|
def build_registry(root, workflow, state)
|
|
31
33
|
checks = workflow.dig("artifact_registry", "content_checks") || {}
|
|
32
34
|
artifacts = Array(workflow["steps"]).flat_map do |step|
|
|
33
|
-
|
|
35
|
+
ArtifactSpecs.for_step(step).map do |output|
|
|
34
36
|
path = interpolate_path(output.fetch("path"), state)
|
|
35
37
|
abs, = DevflowControlIO.resolve_control_path(root, path, :artifact)
|
|
36
38
|
exists = File.exist?(abs)
|
|
@@ -90,9 +92,7 @@ begin
|
|
|
90
92
|
output_path, = DevflowControlIO.resolve_control_path(root, path, :artifact)
|
|
91
93
|
path unless File.exist?(output_path)
|
|
92
94
|
end.compact
|
|
93
|
-
if
|
|
94
|
-
result = { "ok" => false, "classification" => "unsafe", "reason" => "missing_required_output", "missing_outputs" => missing_current_outputs, "plan" => { "state_transition" => "none", "registry_write" => false } }
|
|
95
|
-
elsif !validation["ok"]
|
|
95
|
+
if !validation["ok"]
|
|
96
96
|
result = { "ok" => false, "classification" => "unsafe", "reason" => "ambiguous_or_invalid_state", "issues" => validation["errors"], "plan" => { "state_transition" => "none", "registry_write" => false } }
|
|
97
97
|
elsif !options[:confirm] || !options[:write]
|
|
98
98
|
result = { "ok" => false, "classification" => "safe_repairable", "reason" => "confirmation_required", "revision" => state.fetch("revision", 0), "plan" => { "state_transition" => "none", "registry_write" => true, "registry" => candidate } }
|
|
@@ -8,6 +8,8 @@ require "json"
|
|
|
8
8
|
require "optparse"
|
|
9
9
|
require "yaml"
|
|
10
10
|
require_relative "control_io"
|
|
11
|
+
require_relative "artifact_specs"
|
|
12
|
+
require_relative "gate_policy"
|
|
11
13
|
require_relative "phase_ownership"
|
|
12
14
|
|
|
13
15
|
options = {
|
|
@@ -56,7 +58,7 @@ end
|
|
|
56
58
|
|
|
57
59
|
def collect_outputs(workflow)
|
|
58
60
|
Array(workflow["steps"]).flat_map do |step|
|
|
59
|
-
|
|
61
|
+
ArtifactSpecs.for_step(step).map do |output|
|
|
60
62
|
[step, output]
|
|
61
63
|
end
|
|
62
64
|
end
|
|
@@ -67,6 +69,10 @@ project_lock = DevflowControlIO.acquire_project_lock(root) if options[:write]
|
|
|
67
69
|
begin
|
|
68
70
|
state_path, = DevflowControlIO.resolve_control_path(root, options[:state], :state)
|
|
69
71
|
state = load_state(state_path)
|
|
72
|
+
if state["current_flow"] == "dynamic"
|
|
73
|
+
puts JSON.generate({ "ok" => true, "written" => false, "mode" => "dynamic", "reason" => "task evidence is maintained in STATE; no per-skill artifact registry required" })
|
|
74
|
+
exit 0
|
|
75
|
+
end
|
|
70
76
|
ownership = PhaseOwnership.validate_state(root: root, state: state)
|
|
71
77
|
unless ownership["ok"]
|
|
72
78
|
puts JSON.pretty_generate(ownership.merge("written" => false))
|
|
@@ -33,8 +33,7 @@ module WorkflowStateValidator
|
|
|
33
33
|
# Core quality checks are advisory by default. Keep only structural and
|
|
34
34
|
# write-safety failures hard; AI review decides whether business-quality
|
|
35
35
|
# drift, stale metadata, or incomplete generated evidence should pause work.
|
|
36
|
-
|
|
37
|
-
HARD_VALIDATION_CODES = %w[
|
|
36
|
+
OPERATION_VALIDATION_CODES = %w[
|
|
38
37
|
invalid_control_path
|
|
39
38
|
missing_state_file
|
|
40
39
|
missing_state_front_matter
|
|
@@ -44,36 +43,8 @@ module WorkflowStateValidator
|
|
|
44
43
|
workflow_step_unknown
|
|
45
44
|
phase_ownership_conflict
|
|
46
45
|
].freeze
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
acceptance_results_not_passed
|
|
50
|
-
execution_surface_mismatch
|
|
51
|
-
approval_invalid
|
|
52
|
-
human_result_unapproved
|
|
53
|
-
dsl_not_approved
|
|
54
|
-
environment_profile_not_approved
|
|
55
|
-
runtime_integrity_error
|
|
56
|
-
acceptance_tests_not_passed
|
|
57
|
-
acceptance_tests_empty
|
|
58
|
-
acceptance_test_suite_freeze_failed
|
|
59
|
-
acceptance_test_suite_freeze_attestation_invalid
|
|
60
|
-
acceptance_test_suite_freeze_attestation_failed
|
|
61
|
-
acceptance_test_suite_candidate_binding_failed
|
|
62
|
-
acceptance_test_suite_freeze_recovery_failed
|
|
63
|
-
].freeze
|
|
64
|
-
|
|
65
|
-
def self.hard_validation_code?(code)
|
|
66
|
-
value = code.to_s
|
|
67
|
-
return true if HARD_VALIDATION_CODES.include?(value)
|
|
68
|
-
return true if ACCEPTANCE_HARD_VALIDATION_CODES.include?(value)
|
|
69
|
-
return true if value.start_with?("artifact_registry_", "parallel_", "PARALLEL_")
|
|
70
|
-
return true if value.start_with?("duplicate_", "unknown_", "missing_tracked_step")
|
|
71
|
-
return true if value.start_with?("step_in_", "completed_", "terminal_", "non_completed_")
|
|
72
|
-
return true if value.start_with?("waiting_checkpoint_")
|
|
73
|
-
return true if value.start_with?("skipped_")
|
|
74
|
-
return true if %w[missing_required_output output_not_registered].include?(value)
|
|
75
|
-
|
|
76
|
-
false
|
|
46
|
+
def self.operation_validation_code?(code)
|
|
47
|
+
OPERATION_VALIDATION_CODES.include?(code.to_s)
|
|
77
48
|
end
|
|
78
49
|
|
|
79
50
|
module_function
|
|
@@ -89,6 +60,15 @@ module WorkflowStateValidator
|
|
|
89
60
|
issues = []
|
|
90
61
|
|
|
91
62
|
state = state_override || load_state(state_path, issues)
|
|
63
|
+
if state && state["current_flow"] == "dynamic"
|
|
64
|
+
require_relative "dynamic_plan"
|
|
65
|
+
begin
|
|
66
|
+
DynamicPlan.validate!(root, state.fetch("plan"))
|
|
67
|
+
rescue ArgumentError, KeyError => error
|
|
68
|
+
issues << issue("error", "invalid_dynamic_plan", error.message)
|
|
69
|
+
end
|
|
70
|
+
return result(issues.empty?, state_path, nil, nil, issues)
|
|
71
|
+
end
|
|
92
72
|
return result(false, state_path, nil, nil, issues) unless state
|
|
93
73
|
|
|
94
74
|
REQUIRED_STATE_FIELDS.each do |field|
|
|
@@ -159,14 +139,12 @@ module WorkflowStateValidator
|
|
|
159
139
|
end
|
|
160
140
|
end
|
|
161
141
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
next if hard_validation_code?(entry["code"])
|
|
142
|
+
issues.each do |entry|
|
|
143
|
+
next unless entry["severity"] == "error"
|
|
144
|
+
next if operation_validation_code?(entry["code"])
|
|
166
145
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
end
|
|
146
|
+
entry["severity"] = "warning"
|
|
147
|
+
entry["soft_gate"] = true
|
|
170
148
|
end
|
|
171
149
|
ok = issues.none? { |entry| entry["severity"] == "error" }
|
|
172
150
|
result(ok, state_path, workflow_path, registry_path, issues)
|
|
@@ -107,10 +107,46 @@ def canonical_fingerprint(canonical)
|
|
|
107
107
|
end
|
|
108
108
|
|
|
109
109
|
def read_source_fingerprint(root, feature)
|
|
110
|
-
|
|
111
|
-
|
|
110
|
+
units_path, = DevflowControlIO.resolve_control_path(root, ".devflow/planning/requirements/#{feature}/source_units.yaml", :artifact)
|
|
111
|
+
# Core source unitization owns this value. Both the baseline template and
|
|
112
|
+
# graph-generated traceability views legitimately omit the legacy mirror.
|
|
113
|
+
traceability_path, = DevflowControlIO.resolve_control_path(root, ".devflow/planning/requirements/#{feature}/traceability.json", :artifact)
|
|
114
|
+
legacy = if File.file?(traceability_path)
|
|
115
|
+
traceability = JSON.parse(File.read(traceability_path))
|
|
116
|
+
raise ArgumentError, "traceability.json must be an object" unless traceability.is_a?(Hash)
|
|
117
|
+
traceability["source_document_fingerprint"].to_s
|
|
118
|
+
else
|
|
119
|
+
""
|
|
120
|
+
end
|
|
121
|
+
unless File.exist?(units_path) || File.symlink?(units_path)
|
|
122
|
+
return legacy unless legacy.strip.empty?
|
|
112
123
|
|
|
113
|
-
|
|
124
|
+
raise ArgumentError, "source_units.yaml is missing and traceability.json has no legacy source_document_fingerprint; restore requirement-wayfinder source provenance"
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
units = YAML.safe_load(File.read(units_path), permitted_classes: [], aliases: false)
|
|
128
|
+
raise ArgumentError, "source_units.yaml must be an object" unless units.is_a?(Hash)
|
|
129
|
+
fingerprint = units["source_document_fingerprint"].to_s
|
|
130
|
+
digest = fingerprint.delete_prefix("sha256:")
|
|
131
|
+
unless digest.match?(/\A[0-9a-f]{64}\z/)
|
|
132
|
+
raise ArgumentError, "source_units.yaml source_document_fingerprint must be a SHA-256 digest"
|
|
133
|
+
end
|
|
134
|
+
source = units["source_document"].to_s
|
|
135
|
+
if source.empty? || Pathname.new(source).absolute? || source.split(/[\\\/]/).include?("..")
|
|
136
|
+
raise ArgumentError, "source_units.yaml source_document must be a project-relative path"
|
|
137
|
+
end
|
|
138
|
+
root_real = File.realpath(root)
|
|
139
|
+
source_path = File.realpath(File.join(root_real, source))
|
|
140
|
+
unless source_path.start_with?(root_real + File::SEPARATOR)
|
|
141
|
+
raise ArgumentError, "source_units.yaml source_document escapes project root"
|
|
142
|
+
end
|
|
143
|
+
unless Digest::SHA256.file(source_path).hexdigest == digest
|
|
144
|
+
raise ArgumentError, "source_units.yaml source_document_fingerprint is stale; regenerate source units and review the changed requirements"
|
|
145
|
+
end
|
|
146
|
+
unless legacy.empty? || legacy.delete_prefix("sha256:") == digest
|
|
147
|
+
raise ArgumentError, "traceability.json source_document_fingerprint conflicts with source_units.yaml; reconcile requirement provenance"
|
|
148
|
+
end
|
|
149
|
+
fingerprint
|
|
114
150
|
end
|
|
115
151
|
|
|
116
152
|
def verify_business_action_refs(canonical, path)
|
package/plugins/devflow-core/skills/workflow-orchestrator/templates/feature-development.yaml
CHANGED
|
@@ -13,13 +13,12 @@ phase_scope:
|
|
|
13
13
|
failure_branch_enabled: true
|
|
14
14
|
parallel_window_enabled: true
|
|
15
15
|
|
|
16
|
-
#
|
|
16
|
+
# Gate checks are advisory. Existing STATE
|
|
17
17
|
# files without governance_started_revision remain legacy_unattested and are
|
|
18
18
|
# only reported until their next newly-started governed step.
|
|
19
19
|
governance:
|
|
20
20
|
# Chain skill owns orchestration while Core remains an evidence/diagnostic
|
|
21
|
-
# service.
|
|
22
|
-
hard_gate_mode: chain
|
|
21
|
+
# service. Core checks only produce diagnostics.
|
|
23
22
|
gate_contract_version: 3
|
|
24
23
|
requirement_graph_gate: enforced
|
|
25
24
|
requirement_coverage_gate: enforced
|
|
@@ -3,11 +3,19 @@ name: workflow-pipeline
|
|
|
3
3
|
description: Use when running or resuming the full .devflow/planning delivery pipeline from requirements through implementation, frozen acceptance tests, and Devflow release closeout.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
+
Dynamic plan mode: when Core reports `mode: dynamic`, use `workflow-dynamic` as the execution protocol. The selected task supplies scope, dependencies, required outputs and verification. Use this skill's relevant technical guidance, but do not create legacy stage documents/meta pairs, require fixed checkpoints, or advance legacy STATE. Return activity/evidence to `devflow_plan`; only Core records verification.
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
## 默认软门禁策略
|
|
10
|
+
|
|
11
|
+
工作流硬门禁已移除。下文及各步骤中的产物、索引、合同、审查、质量、恢复和 CP 审批条件均为诊断建议:保留失败证据和缺项,允许继续执行。用户说“下一步”即可推进,无需额外批准弹窗;不得把继续执行记录成人工批准,也不得把失败、未执行的测试改写为通过。真实读写/执行失败、无法解析的状态和并发版本冲突须如实报告。硬门禁及其恢复开关已移除。
|
|
12
|
+
|
|
13
|
+
|
|
6
14
|
<objective>
|
|
7
15
|
全链路研发交付入口。解析用户参数,先确保需求主链“入口分诊 → 探索地图 → 领域建模 → 可实现性审查”完成,再检测当前状态,然后委托 workflow-orchestrator 接管后续所有步骤调度。
|
|
8
16
|
pipeline 本身不包含执行规则——所有门禁、CP 协议、失败分支、并行窗口、lessons 注入规则均由 orchestrator 维护。
|
|
9
17
|
|
|
10
|
-
当前迁移阶段由本 skill 作为链路编排器:按 `next_step.skill` 依次调用各技能、传递上一技能的产物与诊断、再决定下一技能。Core 的 contract/checker/output/review 硬门禁暂不参与阻断;Core
|
|
18
|
+
当前迁移阶段由本 skill 作为链路编排器:按 `next_step.skill` 依次调用各技能、传递上一技能的产物与诊断、再决定下一技能。Core 的 contract/checker/output/review 硬门禁暂不参与阻断;Core 结果只作为事实和风险提示。真实操作无法完成时报告原因;CP 默认带警告继续,仅显式恢复确认时等待人工决定。
|
|
11
19
|
</objective>
|
|
12
20
|
|
|
13
21
|
<process>
|
|
@@ -55,10 +63,10 @@ pipeline 本身不包含执行规则——所有门禁、CP 协议、失败分
|
|
|
55
63
|
|
|
56
64
|
- **`--track A`**:后端/API 轨道产物模式。可跑 `requirement-triage → requirement-wayfinder → domain-modeling → requirement-feasibility-review → requirement-baseline → api-contract → architecture-feasibility → backend-tech-spec → api-call-guide`,但不能自动进入 `integration-verify / change-plan / execute`。
|
|
57
65
|
- **`--track B`**:前端/UI 轨道产物模式。要求 `api-contract` 与 `architecture-feasibility` 已完成;新需求从需求主链开始,已有共同事实可跑 `ui-spec → feature-breakdown → frontend-tech-spec`,但不能自动进入 `change-plan / execute`。
|
|
58
|
-
- **未设置 track
|
|
66
|
+
- **未设置 track**:完整交付模式。完成双轨并执行 `integration-verify` 检查;未通过项作为风险传给 `change-plan`。
|
|
59
67
|
- **`--no-review`**:保留参数位,但当前双轨 workflow 不自动跳过 CP-2。Merge Gate 不能跳过。
|
|
60
68
|
|
|
61
|
-
双轨制原则:API 契约是分叉前的共同事实,`integration-verify`
|
|
69
|
+
双轨制原则:API 契约是分叉前的共同事实,`integration-verify` 是合并前的软门禁。单轨只产出局部轨道文档,不代表完整交付。
|
|
62
70
|
|
|
63
71
|
`workflow-generate-mainflow-cases` 不是独立 workflow step,而是 `run-acceptance-tests` 绑定阶段的组成部分:对执行面为 `ui` / `api_flow` 且绑定为 `automated` 的 TC,必须先生成 v1 DSL 并经 `devflow_approval` 批准执行计划,否则该 TC 保持 blocked。人工 TC 结果同样只经 `human_result` 批准计入聚合门禁。
|
|
64
72
|
|
|
@@ -72,7 +80,7 @@ pipeline 本身不包含执行规则——所有门禁、CP 协议、失败分
|
|
|
72
80
|
2. `workflow-wayfinder` 建立目标、已知事实、边界、迷雾与 ticket;只有活动切片的 L1/L2 迷雾收束后才可完成。
|
|
73
81
|
3. `workflow-domain-modeling` 固化共同语言、所有权、状态和边缘案例。
|
|
74
82
|
4. `requirement-feasibility-review` 以流程证明、代码事实和外部能力检查作为进入基线前的最终门禁。
|
|
75
|
-
5.
|
|
83
|
+
5. 检查审查 meta 的 `baseline_gate`,未通过项保留为诊断并传给 `workflow-requirement-baseline`。
|
|
76
84
|
|
|
77
85
|
若分诊结论为 `already_done`、`wontfix`、`ready_for_human` 或 `needs_info`,停止主链并报告最小下一步。`workflow-demand-clarify` 不再作为固定主链步骤;需要业务问答时,由 Wayfinder 的 Grilling ticket 调用。
|
|
78
86
|
|
|
@@ -113,7 +121,7 @@ devflow_state(action="inspect")
|
|
|
113
121
|
|
|
114
122
|
## 3. 委托 orchestrator
|
|
115
123
|
|
|
116
|
-
|
|
124
|
+
当前pipeline 继续调用 `workflow-orchestrator` 获取步骤拓扑,但由本链路规则串联实际技能调用,不把 Core gate 的 `block` 结果当作自动停止条件:
|
|
117
125
|
|
|
118
126
|
```
|
|
119
127
|
/workflow-orchestrator
|
|
@@ -41,7 +41,7 @@ workflow-pipeline
|
|
|
41
41
|
→ 完成
|
|
42
42
|
```
|
|
43
43
|
|
|
44
|
-
`workflow-generate-mainflow-cases` 不参与独立排序,而是 `run-acceptance-tests` 绑定阶段的组成部分:automated 的 ui / api_flow TC 需要先生成 v1 DSL 并批准执行计划。人工验收结果(经 `human_result`
|
|
44
|
+
`workflow-generate-mainflow-cases` 不参与独立排序,而是 `run-acceptance-tests` 绑定阶段的组成部分:automated 的 ui / api_flow TC 需要先生成 v1 DSL 并批准执行计划。人工验收结果(经 `human_result` 批准)会与自动执行结果聚合,验收失败是软门禁:保留 failed/blocked 结果与风险,默认不阻止流程推进或收口。
|
|
45
45
|
|
|
46
46
|
## 双轨与 Checkpoint 规则
|
|
47
47
|
|
|
@@ -3,6 +3,8 @@ name: workflow-project-context
|
|
|
3
3
|
description: "Use when a project needs database-backed initialization, repository discovery, project facts, code indexing, navigation, or initialization recovery."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
+
门禁策略:默认软门禁,门禁行为以本段为准。本文及引用材料中的前置检查、审查、质量、完成条件和 CP 确认用于诊断;未满足时记录缺项、风险与后续处理,继续可执行的工作,不自动阻断阶段推进。保留真实的 failed/blocked/未审批状态,不把继续执行写成通过或人工批准。硬门禁及其恢复开关已移除。
|
|
7
|
+
|
|
6
8
|
# Project Initialization
|
|
7
9
|
|
|
8
10
|
Initialize project context only. Do not create requirements, approved API contracts, prototypes, roadmaps, commits, tags, repositories, or workflow state.
|
|
@@ -3,6 +3,8 @@ name: workflow-api-call-guide
|
|
|
3
3
|
description: "Use after backend-tech-spec, before integration-verify. Generates a step-by-step API call guide for frontend developers. This is NOT a change list — no code changes, just calling instructions based on the confirmed API contract and backend tech spec."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
+
门禁策略:默认软门禁,门禁行为以本段为准。本文及引用材料中的前置检查、审查、质量、完成条件和 CP 确认用于诊断;未满足时记录缺项、风险与后续处理,继续可执行的工作,不自动阻断阶段推进。保留真实的 failed/blocked/未审批状态,不把继续执行写成通过或人工批准。硬门禁及其恢复开关已移除。
|
|
7
|
+
|
|
6
8
|
# API 调用指南
|
|
7
9
|
|
|
8
10
|
## 定位
|
|
@@ -3,6 +3,8 @@ name: workflow-api-contract
|
|
|
3
3
|
description: "Use after requirement-baseline, before dual-track fork. Defines shared API contracts (endpoints, request/response shapes, data models) that both Track A and Track B reference. This is the fork point — contract changes after this step are exception paths."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
+
门禁策略:默认软门禁,门禁行为以本段为准。本文及引用材料中的前置检查、审查、质量、完成条件和 CP 确认用于诊断;未满足时记录缺项、风险与后续处理,继续可执行的工作,不自动阻断阶段推进。保留真实的 failed/blocked/未审批状态,不把继续执行写成通过或人工批准。硬门禁及其恢复开关已移除。
|
|
7
|
+
|
|
6
8
|
# API 契约生成器
|
|
7
9
|
|
|
8
10
|
## 定位
|
|
@@ -27,7 +29,7 @@ description: "Use after requirement-baseline, before dual-track fork. Defines sh
|
|
|
27
29
|
- `.devflow/planning/features/{功能名}/api-contract.md`
|
|
28
30
|
- `.devflow/planning/features/{功能名}/api-contract.meta.json`
|
|
29
31
|
|
|
30
|
-
The meta file must also set `traceability_status: "passed"` only after every API design has been written to `traceability.json.designs` and `traceability_check.py --stage design` succeeds; otherwise set `failed
|
|
32
|
+
The meta file must also set `traceability_status: "passed"` only after every API design has been written to `traceability.json.designs` and `traceability_check.py --stage design` succeeds; otherwise set `failed`, record the traceability gaps, and continue with these advisories in soft mode.
|
|
31
33
|
|
|
32
34
|
## 输入
|
|
33
35
|
|
|
@@ -3,6 +3,8 @@ name: workflow-api-supplement
|
|
|
3
3
|
description: "Use when Track B (frontend-tech-spec) discovers API gaps that the shared API contract doesn't cover. This is an EXCEPTION PATH — not a normal pipeline step. Submits API supplement requests for Track A evaluation."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
+
门禁策略:默认软门禁,门禁行为以本段为准。本文及引用材料中的前置检查、审查、质量、完成条件和 CP 确认用于诊断;未满足时记录缺项、风险与后续处理,继续可执行的工作,不自动阻断阶段推进。保留真实的 failed/blocked/未审批状态,不把继续执行写成通过或人工批准。硬门禁及其恢复开关已移除。
|
|
7
|
+
|
|
6
8
|
# API 补充需求(异常路径)
|
|
7
9
|
|
|
8
10
|
## 定位
|
|
@@ -3,6 +3,8 @@ name: workflow-architecture-feasibility
|
|
|
3
3
|
description: "Use after API contract and before frontend/backend technical specifications to compare implementation paths against confirmed workload facts and repository-static context. Use for async or scheduled work, batch operations, external calls, fanout, migrations, workload/SLA constraints, or when a feature may not fit the current framework and integrations."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
+
门禁策略:默认软门禁,门禁行为以本段为准。本文及引用材料中的前置检查、审查、质量、完成条件和 CP 确认用于诊断;未满足时记录缺项、风险与后续处理,继续可执行的工作,不自动阻断阶段推进。保留真实的 failed/blocked/未审批状态,不把继续执行写成通过或人工批准。硬门禁及其恢复开关已移除。
|
|
7
|
+
|
|
6
8
|
# 架构可行性与方案比较
|
|
7
9
|
|
|
8
10
|
生成一份由 Core 派生的架构简报;不把自由 Markdown 当作架构真相,也不把仓库声明当成生产容量。
|