prizmkit 1.1.111 → 1.1.112

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 (110) hide show
  1. package/bundled/VERSION.json +3 -3
  2. package/bundled/agents/prizm-dev-team-reviewer.md +12 -1
  3. package/bundled/dev-pipeline/prizmkit_runtime/interoperability.py +1 -0
  4. package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +19 -73
  5. package/bundled/dev-pipeline/scripts/generate-bugfix-prompt.py +283 -421
  6. package/bundled/dev-pipeline/scripts/generate-recovery-prompt.py +1 -1
  7. package/bundled/dev-pipeline/scripts/generate-refactor-prompt.py +259 -481
  8. package/bundled/dev-pipeline/scripts/init-bugfix-pipeline.py +34 -0
  9. package/bundled/dev-pipeline/scripts/init-pipeline.py +12 -0
  10. package/bundled/dev-pipeline/scripts/init-refactor-pipeline.py +24 -1
  11. package/bundled/dev-pipeline/scripts/prompt_framework.py +312 -0
  12. package/bundled/dev-pipeline/scripts/update-bug-status.py +87 -20
  13. package/bundled/dev-pipeline/scripts/update-feature-status.py +82 -16
  14. package/bundled/dev-pipeline/scripts/update-refactor-status.py +68 -28
  15. package/bundled/dev-pipeline/scripts/utils.py +171 -0
  16. package/bundled/dev-pipeline/templates/bootstrap-prompt.md +1 -1
  17. package/bundled/dev-pipeline/templates/bootstrap-tier2.md +10 -18
  18. package/bundled/dev-pipeline/templates/bootstrap-tier3.md +14 -25
  19. package/bundled/dev-pipeline/templates/bug-fix-list-schema.json +9 -1
  20. package/bundled/dev-pipeline/templates/bugfix-bootstrap-prompt.md +11 -8
  21. package/bundled/dev-pipeline/templates/refactor-bootstrap-prompt.md +122 -182
  22. package/bundled/dev-pipeline/templates/sections/bugfix-artifacts.md +16 -0
  23. package/bundled/dev-pipeline/templates/sections/bugfix-critical-paths.md +12 -0
  24. package/bundled/dev-pipeline/templates/sections/bugfix-mission.md +9 -0
  25. package/bundled/dev-pipeline/templates/sections/bugfix-phase-commit-report.md +31 -0
  26. package/bundled/dev-pipeline/templates/sections/bugfix-phase-critic.md +22 -0
  27. package/bundled/dev-pipeline/templates/sections/bugfix-phase-diagnose-plan.md +23 -0
  28. package/bundled/dev-pipeline/templates/sections/bugfix-phase-implement.md +20 -0
  29. package/bundled/dev-pipeline/templates/sections/bugfix-phase-init.md +9 -0
  30. package/bundled/dev-pipeline/templates/sections/bugfix-phase-manual-verification.md +15 -0
  31. package/bundled/dev-pipeline/templates/sections/bugfix-phase-review.md +29 -0
  32. package/bundled/dev-pipeline/templates/sections/bugfix-reminders.md +11 -0
  33. package/bundled/dev-pipeline/templates/sections/bugfix-session-context.md +7 -0
  34. package/bundled/dev-pipeline/templates/sections/bugfix-session-status.md +31 -0
  35. package/bundled/dev-pipeline/templates/sections/bugfix-task-contract.md +40 -0
  36. package/bundled/dev-pipeline/templates/sections/failure-capture.md +2 -2
  37. package/bundled/dev-pipeline/templates/sections/phase-browser-verification-auto.md +1 -1
  38. package/bundled/dev-pipeline/templates/sections/refactor-artifacts.md +17 -0
  39. package/bundled/dev-pipeline/templates/sections/refactor-critical-paths.md +13 -0
  40. package/bundled/dev-pipeline/templates/sections/refactor-mission.md +9 -0
  41. package/bundled/dev-pipeline/templates/sections/refactor-phase-commit-report.md +37 -0
  42. package/bundled/dev-pipeline/templates/sections/refactor-phase-critic.md +22 -0
  43. package/bundled/dev-pipeline/templates/sections/refactor-phase-implement.md +16 -0
  44. package/bundled/dev-pipeline/templates/sections/refactor-phase-init.md +9 -0
  45. package/bundled/dev-pipeline/templates/sections/refactor-phase-plan.md +22 -0
  46. package/bundled/dev-pipeline/templates/sections/refactor-phase-review.md +19 -0
  47. package/bundled/dev-pipeline/templates/sections/refactor-reminders.md +11 -0
  48. package/bundled/dev-pipeline/templates/sections/refactor-session-context.md +7 -0
  49. package/bundled/dev-pipeline/templates/sections/refactor-session-status.md +28 -0
  50. package/bundled/dev-pipeline/templates/sections/refactor-task-contract.md +52 -0
  51. package/bundled/dev-pipeline/tests/test_generate_bootstrap_prompt.py +17 -5
  52. package/bundled/dev-pipeline/tests/test_python_runner_parity.py +197 -0
  53. package/bundled/dev-pipeline/tests/test_unified_cli.py +6 -2
  54. package/bundled/dev-pipeline/tests/test_utils.py +66 -1
  55. package/bundled/skills/_metadata.json +1 -1
  56. package/bundled/skills/app-planner/SKILL.md +8 -7
  57. package/bundled/skills/app-planner/references/project-brief-guide.md +2 -2
  58. package/bundled/skills/app-planner/references/rules/backend/derivation-rules.md +1 -1
  59. package/bundled/skills/app-planner/references/rules/frontend/derivation-rules.md +1 -1
  60. package/bundled/skills/app-planner/references/rules-configuration.md +53 -26
  61. package/bundled/skills/bug-fix-workflow/SKILL.md +191 -336
  62. package/bundled/skills/bug-planner/SKILL.md +6 -5
  63. package/bundled/skills/bug-planner/references/critic-and-verification.md +3 -3
  64. package/bundled/skills/bug-planner/references/schema-validation.md +2 -1
  65. package/bundled/skills/bug-planner/scripts/validate-bug-list.py +30 -1
  66. package/bundled/skills/bugfix-pipeline-launcher/SKILL.md +16 -16
  67. package/bundled/skills/bugfix-pipeline-launcher/references/configuration.md +2 -2
  68. package/bundled/skills/feature-pipeline-launcher/SKILL.md +19 -11
  69. package/bundled/skills/feature-planner/SKILL.md +9 -11
  70. package/bundled/skills/feature-planner/assets/planning-guide.md +1 -1
  71. package/bundled/skills/feature-planner/references/decomposition-patterns.md +1 -1
  72. package/bundled/skills/feature-planner/references/error-recovery.md +2 -1
  73. package/bundled/skills/feature-planner/references/incremental-feature-planning.md +2 -2
  74. package/bundled/skills/feature-planner/references/new-project-planning.md +35 -39
  75. package/bundled/skills/feature-planner/scripts/validate-and-generate.py +1 -1
  76. package/bundled/skills/feature-workflow/SKILL.md +169 -298
  77. package/bundled/skills/prizmkit/SKILL.md +103 -57
  78. package/bundled/skills/prizmkit-code-review/SKILL.md +97 -116
  79. package/bundled/skills/prizmkit-code-review/references/review-report-template.md +1 -0
  80. package/bundled/skills/prizmkit-code-review/references/reviewer-agent-prompt.md +12 -1
  81. package/bundled/skills/prizmkit-committer/SKILL.md +59 -47
  82. package/bundled/skills/prizmkit-deploy/SKILL.md +162 -381
  83. package/bundled/skills/prizmkit-deploy/references/ssh-adapter-flow.md +220 -0
  84. package/bundled/skills/prizmkit-implement/SKILL.md +51 -40
  85. package/bundled/skills/prizmkit-init/SKILL.md +4 -3
  86. package/bundled/skills/prizmkit-plan/SKILL.md +85 -70
  87. package/bundled/skills/prizmkit-prizm-docs/SKILL.md +94 -73
  88. package/bundled/skills/prizmkit-prizm-docs/assets/prizm-docs-format.md +20 -18
  89. package/bundled/skills/prizmkit-prizm-docs/references/op-update.md +18 -14
  90. package/bundled/skills/prizmkit-retrospective/SKILL.md +56 -48
  91. package/bundled/skills/prizmkit-retrospective/references/structural-sync-steps.md +79 -27
  92. package/bundled/skills/prizmkit-test/SKILL.md +138 -141
  93. package/bundled/skills/prizmkit-test/references/examples.md +10 -8
  94. package/bundled/skills/prizmkit-test/references/test-generation-steps.md +1 -1
  95. package/bundled/skills/prizmkit-test/references/test-report-template.md +2 -2
  96. package/bundled/skills/recovery-workflow/SKILL.md +195 -256
  97. package/bundled/skills/recovery-workflow/evals/evals.json +21 -13
  98. package/bundled/skills/recovery-workflow/references/detection.md +48 -39
  99. package/bundled/skills/recovery-workflow/scripts/detect-recovery-state.py +258 -322
  100. package/bundled/skills/refactor-pipeline-launcher/SKILL.md +29 -11
  101. package/bundled/skills/refactor-planner/SKILL.md +2 -2
  102. package/bundled/skills/refactor-planner/references/behavior-preservation.md +24 -19
  103. package/bundled/skills/refactor-planner/references/fast-path.md +2 -4
  104. package/bundled/skills/refactor-planner/references/planning-phases.md +2 -2
  105. package/bundled/skills/refactor-workflow/SKILL.md +173 -307
  106. package/package.json +1 -1
  107. package/src/scaffold.js +5 -0
  108. package/bundled/dev-pipeline/templates/agent-prompts/reviewer-review.md +0 -6
  109. package/bundled/skills/feature-workflow/references/brainstorm-guide.md +0 -137
  110. package/bundled/skills/refactor-workflow/references/brainstorm-guide.md +0 -116
@@ -61,6 +61,35 @@ def _write_feature_list(paths):
61
61
  )
62
62
 
63
63
 
64
+ def _write_family_plan(paths, kind, items):
65
+ plan_config = {
66
+ "feature": (paths.feature_plan, "features", "dev-pipeline-feature-list-v1"),
67
+ "bugfix": (paths.bugfix_plan, "bugs", "dev-pipeline-bug-fix-list-v1"),
68
+ "refactor": (paths.refactor_plan, "refactors", "dev-pipeline-refactor-list-v1"),
69
+ }
70
+ plan_path, list_key, schema = plan_config[kind]
71
+ plan_path.write_text(
72
+ json.dumps({"$schema": schema, "project_name": "Fake", list_key: items}),
73
+ encoding="utf-8",
74
+ )
75
+ return plan_path
76
+
77
+
78
+ def _get_next_result(kind, paths):
79
+ from prizmkit_runtime.runner_models import family_for, parse_invocation
80
+ from prizmkit_runtime.runner_status import get_next
81
+
82
+ family = family_for(kind, paths)
83
+ invocation = parse_invocation(family, "run", ())
84
+ invocation = invocation.__class__(**{**invocation.__dict__, "list_path": family.plan_path})
85
+ return get_next(family, invocation, paths.project_root)
86
+
87
+
88
+ def _item_id_from_next_result(kind, result):
89
+ key = {"feature": "feature_id", "bugfix": "bug_id", "refactor": "refactor_id"}[kind]
90
+ return result.data[key]
91
+
92
+
64
93
  def test_retired_shell_runtime_files_are_absent_from_source():
65
94
  retired = [
66
95
  "run-feature.sh",
@@ -360,6 +389,174 @@ def test_status_bridge_preserves_feature_filter_selection(tmp_path):
360
389
  assert update.data["context_overflow_count"] == 1
361
390
 
362
391
 
392
+ def test_status_bridge_uses_stable_dependency_order_across_pipeline_families(tmp_path):
393
+ cases = {
394
+ "feature": [
395
+ {"id": "F-001", "title": "Blocked", "status": "pending", "priority": "critical", "dependencies": ["F-002"]},
396
+ {"id": "F-002", "title": "First", "status": "pending", "priority": "low", "dependencies": []},
397
+ {"id": "F-003", "title": "Independent", "status": "pending", "priority": "high", "dependencies": []},
398
+ ],
399
+ "bugfix": [
400
+ {"id": "B-001", "title": "Blocked", "status": "pending", "severity": "critical", "priority": "high", "error_source": {"type": "user_report"}, "verification_type": "manual", "acceptance_criteria": ["fixed"], "dependencies": ["B-002"]},
401
+ {"id": "B-002", "title": "First", "status": "pending", "severity": "low", "priority": "low", "error_source": {"type": "user_report"}, "verification_type": "manual", "acceptance_criteria": ["fixed"], "dependencies": []},
402
+ {"id": "B-003", "title": "Independent", "status": "pending", "severity": "high", "priority": "high", "error_source": {"type": "user_report"}, "verification_type": "manual", "acceptance_criteria": ["fixed"], "dependencies": []},
403
+ ],
404
+ "refactor": [
405
+ {"id": "R-001", "title": "Blocked", "description": "blocked", "scope": {}, "type": "restructure", "priority": "critical", "complexity": "high", "behavior_preservation": {"strategy": "test-gate"}, "acceptance_criteria": ["done"], "dependencies": ["R-002"], "status": "pending"},
406
+ {"id": "R-002", "title": "First", "description": "first", "scope": {}, "type": "extract", "priority": "low", "complexity": "low", "behavior_preservation": {"strategy": "test-gate"}, "acceptance_criteria": ["done"], "dependencies": [], "status": "pending"},
407
+ {"id": "R-003", "title": "Independent", "description": "independent", "scope": {}, "type": "simplify", "priority": "high", "complexity": "medium", "behavior_preservation": {"strategy": "test-gate"}, "acceptance_criteria": ["done"], "dependencies": [], "status": "pending"},
408
+ ],
409
+ }
410
+
411
+ for kind, items in cases.items():
412
+ paths = _make_paths(tmp_path / f"stable-{kind}")
413
+ _write_family_plan(paths, kind, items)
414
+
415
+ result = _get_next_result(kind, paths)
416
+
417
+ assert result.ok
418
+ assert _item_id_from_next_result(kind, result) == {"feature": "F-002", "bugfix": "B-002", "refactor": "R-002"}[kind]
419
+
420
+
421
+ def test_status_bridge_prefers_in_progress_items_with_satisfied_dependencies(tmp_path):
422
+ cases = {
423
+ "feature": [
424
+ {"id": "F-001", "title": "Done", "status": "completed", "priority": "low", "dependencies": []},
425
+ {"id": "F-002", "title": "Resume", "status": "in_progress", "priority": "low", "dependencies": ["F-001"]},
426
+ {"id": "F-003", "title": "Pending", "status": "pending", "priority": "critical", "dependencies": []},
427
+ ],
428
+ "bugfix": [
429
+ {"id": "B-001", "title": "Done", "status": "completed", "severity": "low", "priority": "low", "error_source": {"type": "user_report"}, "verification_type": "manual", "acceptance_criteria": ["fixed"], "dependencies": []},
430
+ {"id": "B-002", "title": "Resume", "status": "in_progress", "severity": "low", "priority": "low", "error_source": {"type": "user_report"}, "verification_type": "manual", "acceptance_criteria": ["fixed"], "dependencies": ["B-001"]},
431
+ {"id": "B-003", "title": "Pending", "status": "pending", "severity": "critical", "priority": "high", "error_source": {"type": "user_report"}, "verification_type": "manual", "acceptance_criteria": ["fixed"], "dependencies": []},
432
+ ],
433
+ "refactor": [
434
+ {"id": "R-001", "title": "Done", "description": "done", "scope": {}, "type": "extract", "priority": "low", "complexity": "low", "behavior_preservation": {"strategy": "test-gate"}, "acceptance_criteria": ["done"], "dependencies": [], "status": "completed"},
435
+ {"id": "R-002", "title": "Resume", "description": "resume", "scope": {}, "type": "extract", "priority": "low", "complexity": "low", "behavior_preservation": {"strategy": "test-gate"}, "acceptance_criteria": ["done"], "dependencies": ["R-001"], "status": "in_progress"},
436
+ {"id": "R-003", "title": "Pending", "description": "pending", "scope": {}, "type": "migrate", "priority": "critical", "complexity": "high", "behavior_preservation": {"strategy": "test-gate"}, "acceptance_criteria": ["done"], "dependencies": [], "status": "pending"},
437
+ ],
438
+ }
439
+
440
+ for kind, items in cases.items():
441
+ paths = _make_paths(tmp_path / f"resume-{kind}")
442
+ _write_family_plan(paths, kind, items)
443
+
444
+ result = _get_next_result(kind, paths)
445
+
446
+ assert result.ok
447
+ assert _item_id_from_next_result(kind, result) == {"feature": "F-002", "bugfix": "B-002", "refactor": "R-002"}[kind]
448
+
449
+
450
+ def test_refactor_dependencies_require_completed_not_other_terminal_statuses(tmp_path):
451
+ paths = _make_paths(tmp_path)
452
+ _write_family_plan(
453
+ paths,
454
+ "refactor",
455
+ [
456
+ {"id": "R-001", "title": "Failed Dependency", "description": "failed", "scope": {}, "type": "extract", "priority": "high", "complexity": "low", "behavior_preservation": {"strategy": "test-gate"}, "acceptance_criteria": ["done"], "dependencies": [], "status": "failed"},
457
+ {"id": "R-002", "title": "Blocked", "description": "blocked", "scope": {}, "type": "restructure", "priority": "high", "complexity": "medium", "behavior_preservation": {"strategy": "test-gate"}, "acceptance_criteria": ["done"], "dependencies": ["R-001"], "status": "pending"},
458
+ ],
459
+ )
460
+
461
+ result = _get_next_result("refactor", paths)
462
+
463
+ assert result.ok
464
+ assert result.text == "PIPELINE_BLOCKED"
465
+ assert result.data is None
466
+
467
+
468
+ def test_init_scripts_reject_missing_self_and_cycle_dependencies(tmp_path):
469
+ script_config = {
470
+ "feature": ("init-pipeline.py", "--feature-list", "F-", "missing dependency"),
471
+ "bugfix": ("init-bugfix-pipeline.py", "--bug-list", "B-", "missing dependency"),
472
+ "refactor": ("init-refactor-pipeline.py", "--refactor-list", "R-", "missing dependency"),
473
+ }
474
+ scenarios = {
475
+ "missing": lambda kind, prefix: _family_items(kind, [
476
+ (f"{prefix}001", [f"{prefix}999"], "pending"),
477
+ ]),
478
+ "self": lambda kind, prefix: _family_items(kind, [
479
+ (f"{prefix}001", [f"{prefix}001"], "pending"),
480
+ ]),
481
+ "cycle": lambda kind, prefix: _family_items(kind, [
482
+ (f"{prefix}001", [f"{prefix}002"], "pending"),
483
+ (f"{prefix}002", [f"{prefix}001"], "pending"),
484
+ ]),
485
+ }
486
+ expected_fragments = {
487
+ "missing": "missing dependency",
488
+ "self": "cannot depend on itself",
489
+ "cycle": "Dependency cycle detected",
490
+ }
491
+
492
+ for kind, (script_name, list_arg, prefix, _missing_fragment) in script_config.items():
493
+ for scenario, build_items in scenarios.items():
494
+ paths = _make_paths(tmp_path / f"init-{kind}-{scenario}")
495
+ plan_path = _write_family_plan(paths, kind, build_items(kind, prefix))
496
+ result = subprocess.run(
497
+ [
498
+ PYTHON_310_PLUS,
499
+ str(PIPELINE_ROOT / "scripts" / script_name),
500
+ list_arg,
501
+ str(plan_path),
502
+ "--state-dir",
503
+ str(paths.project_root / ".prizmkit" / "state" / f"init-{kind}-{scenario}"),
504
+ ],
505
+ cwd=REPO_ROOT,
506
+ stdout=subprocess.PIPE,
507
+ stderr=subprocess.PIPE,
508
+ text=True,
509
+ check=False,
510
+ )
511
+
512
+ assert result.returncode == 1, (kind, scenario, result.stdout, result.stderr)
513
+ payload = json.loads(result.stdout)
514
+ assert any(expected_fragments[scenario] in error for error in payload["errors"])
515
+
516
+
517
+ def _family_items(kind, entries):
518
+ items = []
519
+ for item_id, dependencies, status in entries:
520
+ if kind == "feature":
521
+ items.append({
522
+ "id": item_id,
523
+ "title": item_id,
524
+ "description": "Implement this feature with enough detail for initialization validation.",
525
+ "priority": "medium",
526
+ "dependencies": dependencies,
527
+ "acceptance_criteria": ["done"],
528
+ "status": status,
529
+ })
530
+ elif kind == "bugfix":
531
+ items.append({
532
+ "id": item_id,
533
+ "title": item_id,
534
+ "description": "Bug description",
535
+ "severity": "medium",
536
+ "priority": "medium",
537
+ "error_source": {"type": "user_report"},
538
+ "verification_type": "manual",
539
+ "acceptance_criteria": ["fixed"],
540
+ "dependencies": dependencies,
541
+ "status": status,
542
+ })
543
+ else:
544
+ items.append({
545
+ "id": item_id,
546
+ "title": item_id,
547
+ "description": "Refactor description",
548
+ "scope": {},
549
+ "type": "extract",
550
+ "priority": "medium",
551
+ "complexity": "low",
552
+ "behavior_preservation": {"strategy": "test-gate"},
553
+ "acceptance_criteria": ["done"],
554
+ "dependencies": dependencies,
555
+ "status": status,
556
+ })
557
+ return items
558
+
559
+
363
560
  def test_status_bridge_passes_max_retries_from_invocation_or_environment(monkeypatch, tmp_path):
364
561
  import subprocess
365
562
  from prizmkit_runtime.runner_models import family_for, parse_invocation
@@ -458,7 +458,7 @@ def test_interoperability_preserves_existing_utility_mapping():
458
458
  explicitly_library_only = set(UTILITY_LIBRARY_ONLY)
459
459
 
460
460
  assert actual_scripts == mapped_scripts | explicitly_library_only
461
- assert explicitly_library_only == {"continuation.py", "utils.py"}
461
+ assert explicitly_library_only == {"continuation.py", "prompt_framework.py", "utils.py"}
462
462
  assert "parse-stream-progress.py" in mapped_scripts
463
463
  assert "prizmkit-test-gate.py" not in mapped_scripts
464
464
 
@@ -803,7 +803,11 @@ def test_headless_subagent_prompt_guidance_keeps_no_worktree_boundary_visible():
803
803
 
804
804
  for path in guidance_files:
805
805
  text = path.read_text(encoding="utf-8")
806
- assert "default/no worktree" in text, path
806
+ assert (
807
+ "default/no worktree" in text
808
+ or "current-workspace / active-checkout / no-worktree" in text
809
+ or "current workspace / active checkout / no worktree" in text
810
+ ), path
807
811
  assert "do not request or create" in text.lower() or "do not request worktree isolation" in text.lower(), path
808
812
  assert ".claude/worktrees/..." in text, path
809
813
  assert ".prizmkit/state/worktrees/..." in text, path
@@ -4,7 +4,13 @@ import json
4
4
  import os
5
5
  import tempfile
6
6
 
7
- from utils import load_json_file, write_json_file, pad_right, _build_progress_bar
7
+ from utils import (
8
+ load_json_file,
9
+ write_json_file,
10
+ pad_right,
11
+ _build_progress_bar,
12
+ stable_topological_order,
13
+ )
8
14
 
9
15
 
10
16
  # ---------------------------------------------------------------------------
@@ -80,3 +86,62 @@ class TestBuildProgressBar:
80
86
  def test_fifty_percent(self):
81
87
  bar = _build_progress_bar(50, width=10)
82
88
  assert "50%" in bar
89
+
90
+
91
+ # ---------------------------------------------------------------------------
92
+ # stable_topological_order
93
+ # ---------------------------------------------------------------------------
94
+
95
+ class TestStableTopologicalOrder:
96
+ def test_dependencies_precede_dependents_with_stable_tie_breaking(self):
97
+ items = [
98
+ {"id": "F-001", "dependencies": []},
99
+ {"id": "F-002", "dependencies": ["F-001"]},
100
+ {"id": "F-003", "dependencies": []},
101
+ ]
102
+
103
+ ordered, errors = stable_topological_order(items, item_label="Feature")
104
+
105
+ assert errors == []
106
+ assert [item["id"] for item in ordered] == ["F-001", "F-002", "F-003"]
107
+
108
+ def test_unrelated_items_preserve_original_list_order(self):
109
+ items = [
110
+ {"id": "F-010", "dependencies": []},
111
+ {"id": "F-002", "dependencies": []},
112
+ {"id": "F-007", "dependencies": []},
113
+ ]
114
+
115
+ ordered, errors = stable_topological_order(items, item_label="Feature")
116
+
117
+ assert errors == []
118
+ assert [item["id"] for item in ordered] == ["F-010", "F-002", "F-007"]
119
+
120
+ def test_missing_dependency_is_blocking_error(self):
121
+ items = [
122
+ {"id": "B-001", "dependencies": ["B-999"]},
123
+ ]
124
+
125
+ _ordered, errors = stable_topological_order(items, item_label="Bug")
126
+
127
+ assert errors == ["Bug 'B-001' depends on missing dependency 'B-999'"]
128
+
129
+ def test_self_dependency_is_blocking_error(self):
130
+ items = [
131
+ {"id": "R-001", "dependencies": ["R-001"]},
132
+ ]
133
+
134
+ _ordered, errors = stable_topological_order(items, item_label="Refactor")
135
+
136
+ assert errors == ["Refactor 'R-001' cannot depend on itself"]
137
+
138
+ def test_cycle_reports_chain(self):
139
+ items = [
140
+ {"id": "F-001", "dependencies": ["F-003"]},
141
+ {"id": "F-002", "dependencies": ["F-001"]},
142
+ {"id": "F-003", "dependencies": ["F-002"]},
143
+ ]
144
+
145
+ _ordered, errors = stable_topological_order(items, item_label="Feature")
146
+
147
+ assert errors == ["Dependency cycle detected: F-001 -> F-003 -> F-002 -> F-001"]
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.1.111",
2
+ "version": "1.1.112",
3
3
  "skills": {
4
4
  "prizmkit": {
5
5
  "description": "Full-lifecycle dev toolkit. Covers spec-driven development, Prizm context docs, code quality, debugging, deployment, and knowledge management.",
@@ -35,10 +35,11 @@ If you believe the task is better suited for a different workflow, you MUST:
35
35
 
36
36
  **Your ONLY writable outputs are:**
37
37
  1. `.prizmkit/plans/project-brief.md` (`.prizmkit/plans/` — accumulated project context brief)
38
- 2. Project conventions and architecture decisions appended to `AGENTS.md` / `CLAUDE.md` / `CODEBUDDY.md` (with user consent)
39
- 3. Infrastructure configuration (database conventions, deployment config, **cloud services**) appended to `AGENTS.md` / `CLAUDE.md` / `CODEBUDDY.md` `### Infrastructure` section
40
- 4. `.prizmkit/rules/<layer>-rules.md` per-layer development rules generated by Rules Configuration
41
- 5. `.prizmkit/prizm-docs/root.prizm` — `RULES:` pointer line only (not other root.prizm content; created minimally if absent)
38
+ 2. `.prizmkit/plans/project-brief.draft.md` draft only when user exits before completion
39
+ 3. Project conventions and architecture decisions appended to `AGENTS.md` / `CLAUDE.md` / `CODEBUDDY.md` (with user consent)
40
+ 4. Infrastructure configuration (database conventions, deployment config, **cloud services**) appended to `AGENTS.md` / `CLAUDE.md` / `CODEBUDDY.md` `### Infrastructure` section
41
+ 5. `.prizmkit/rules/<layer>-rules.md` — per-layer development rules generated by Rules Configuration
42
+ 6. `.prizmkit/prizm-docs/root.prizm` — `RULES:` pointer line only (not other root.prizm content; created minimally if absent)
42
43
 
43
44
  **Project instruction file selection**:
44
45
  - If `.prizmkit/manifest.json` exists, use its `platform` field as the source of truth.
@@ -172,7 +173,7 @@ Route by answer:
172
173
  - After brainstorm completes, use `AskUserQuestion`: "Ideas are taking shape. What's next?"
173
174
  - **Continue to project planning (Recommended)** — capture tech stack, conventions, and architecture decisions
174
175
  - **Continue refining** — keep brainstorming
175
- - **Save draft & exit** — save progress to .prizmkit/planning/
176
+ - **Save draft & exit** — save progress to `.prizmkit/plans/project-brief.draft.md`
176
177
  - **Checkpoints in explore mode**: CP-AP-0 (required), CP-AP-1 (required), CP-AP-2 (from brainstorm output), CP-AP-3 (only if user proceeds to Phase 2), CP-AP-4 and CP-AP-5 (only if user transitions to produce mode)
177
178
  - **Generate project context only** → Enter **Quick Context Mode** (brownfield only):
178
179
  - Run Project State Detection → if greenfield, redirect to produce mode
@@ -271,7 +272,7 @@ When the session appears to be ending:
271
272
  1. **Remind**: "You set out to produce a project plan but `.prizmkit/plans/project-brief.md` isn't complete yet."
272
273
  2. Use `AskUserQuestion`: "How would you like to proceed?"
273
274
  - **Continue to completion (Recommended)** — finish the project brief
274
- - **Save draft & exit** — write current progress as draft to `.prizmkit/planning/`
275
+ - **Save draft & exit** — write current progress as draft to `.prizmkit/plans/project-brief.draft.md`
275
276
  - **Abandon** — exit without saving
276
277
 
277
278
  ## Completion Summary
@@ -281,7 +282,7 @@ After all checkpoints pass, present a summary and end the session:
281
282
  1. **Summary** (as text): List all project-level artifacts produced:
282
283
  - Project conventions → `AGENTS.md` / `CLAUDE.md` / `CODEBUDDY.md` `### Project Conventions`
283
284
  - Infrastructure config → `AGENTS.md` / `CLAUDE.md` / `CODEBUDDY.md` `### Infrastructure` (database conventions + deployment config)
284
- - Tech stack → `.prizmkit/config.json`
285
+ - Tech stack assumptions captured in `.prizmkit/plans/project-brief.md` and reused from `.prizmkit/config.json` when it already exists
285
286
  - Architecture decisions (if any) → `AGENTS.md` / `CLAUDE.md` / `CODEBUDDY.md` `### Architecture Decisions`
286
287
  - Dev rules (if configured) → `.prizmkit/rules/<layer>-rules.md` (with `root.prizm` `RULES:` pointer)
287
288
  - Project brief → `.prizmkit/plans/project-brief.md`
@@ -1,6 +1,6 @@
1
1
  # Project Brief Template
2
2
 
3
- > Capture the user's key product ideas as a simple checklist. Each line is one idea. This file is referenced by `root.prizm` (`PROJECT_BRIEF:`) so every new AI session automatically knows the project's goals.
3
+ > Capture the user's key product ideas as a simple checklist. Each line is one idea. Downstream planners can load this file as project-level context.
4
4
 
5
5
  ## File Location
6
6
 
@@ -70,7 +70,7 @@ Stop asking when ALL of these are clear:
70
70
  2. **Users** — who will use it and in what context
71
71
  3. **Core features** — at least 3 concrete capabilities (not vague aspirations)
72
72
  4. **Boundaries** — what's in scope vs. out of scope for V1
73
- 5. **Technical direction** — enough to populate `config.json` tech_stack (or explicitly deferred)
73
+ 5. **Technical direction** — enough to describe stack assumptions in the brief, or explicitly mark them deferred
74
74
 
75
75
  If the user gives short/vague answers, don't accept them — rephrase and ask again. A weak brief leads to weak specs downstream.
76
76
 
@@ -614,6 +614,6 @@ This table verifies every template placeholder has at least one derivation rule
614
614
  | `{{ ai_dependency_rule }}` | Yes | No | D-AI-DEP-STRICT-01, D-AI-DEP-ANNOUNCE-01, D-AI-DEP-LOOSE-01 |
615
615
  | `{{ ai_breaking_change_rule }}` | Yes | No | D-AI-BREAK-STRICT-01, D-AI-BREAK-LOOSE-01 |
616
616
  | `{{ ai_config_rule }}` | Yes | No | D-AI-CONFIG-STRICT-01, D-AI-CONFIG-SYNC-01 |
617
- | `{{ FIXED_RULES_* }}` | No | Yes | `references/fixed-rules.md` |
617
+ | `{{ FIXED_RULES_* }}` | No | Yes | `references/rules/backend/fixed-rules.md` |
618
618
  | `{{ deny_list_summary }}` | N/A | N/A | AI auto-generates in Phase 4 |
619
619
  | `{{ recommended_libs }}` | N/A | N/A | AI auto-generates in Phase 4 |
@@ -2,7 +2,7 @@
2
2
 
3
3
  > This file defines the mapping "Phase 2 answers → rule blocks auto-injected into frontend-rules.md". The AI reads this file in Phase 3 — **do not ask the user again**.
4
4
  >
5
- > Usage: Each rule block has a unique ID (e.g., `D-TAILWIND-01`). After matching the user's answer, inject the "rule body" verbatim into the corresponding `{{ ... }}` placeholder in [frontend-rules.template.md](../templates/frontend-rules.template.md).
5
+ > Usage: Each rule block has a unique ID (e.g., `D-TAILWIND-01`). After matching the user's answer, inject the "rule body" verbatim into the corresponding `{{ ... }}` placeholder in [`template.md`](template.md).
6
6
 
7
7
  ---
8
8
 
@@ -1,6 +1,28 @@
1
- # Rules Configuration — Detailed Q&A Procedures
1
+ # Rules Configuration — Full Procedure
2
2
 
3
- Procedural details for configuring per-layer development rules during app planning. SKILL.md retains Steps 1-3 and 5 (flow control); this file contains Step 4's full Q&A workflow.
3
+ Procedure for configuring per-layer development rules during app planning.
4
+
5
+ ## Step 1: Detect Layers
6
+
7
+ - Prefer `.prizmkit/config.json` `tech_stack` when present.
8
+ - Otherwise infer layers from project files:
9
+ - frontend: `package.json` plus React/Vue/Svelte/Next/Vite indicators
10
+ - backend: Express/FastAPI/Django/Go/Java service indicators
11
+ - database: migrations, ORM config, schema files, or DB dependencies
12
+ - mobile: Flutter/React Native/iOS/Android indicators
13
+ - Present detected layers with `AskUserQuestion`; include "Skip rules configuration" as a non-blocking option.
14
+
15
+ ## Step 2: Choose Configuration Mode
16
+
17
+ - Quick mode asks only manifest groups marked `quick_mode: true`.
18
+ - Full mode asks every manifest group in order.
19
+ - Skip mode records no rules file for that layer and continues without reminders.
20
+
21
+ ## Step 3: Ask Questions with AskUserQuestion
22
+
23
+ - Convert each question-bank option group into selectable `AskUserQuestion` options.
24
+ - Do not ask users to type letter shortcuts such as `A` or `1`; those shortcuts are legacy notation in question banks only.
25
+ - Every non-essential question includes a skip/defer option.
4
26
 
5
27
  ## Step 4: Configure Each Selected Layer — Full Q&A Workflow
6
28
 
@@ -10,37 +32,42 @@ For each selected layer, run the 4-phase rule generation pattern:
10
32
 
11
33
  - Read `${SKILL_DIR}/references/rules/<layer>/fixed-rules.md` — industry-consensus rules injected without asking
12
34
  - Read `${SKILL_DIR}/references/rules/<layer>/question-bank.md` — interactive questions organized in groups (G1->G10): authoritative source for question text, options, "Recommended" markers, and Notes
13
- - Read `${SKILL_DIR}/references/rules/<layer>/question-manifest.json` — machine-readable structure for this layer. **This is your asking checklist.** It lists, per question: `group`, `required`, `maps_to` (which template placeholders the answer fills), and conditional fields (`required_if`, `auto_derived_when`, `options_vary_by`). Its `groups[]` carries each group's `quick_mode` flag, and `template_placeholders` is the expected-set for the Phase D self-check. Use the manifest to track coverage; use question-bank.md for the actual wording you present to the user. If a question id in one file is absent from the other, trust question-bank.md for content and note the drift.
35
+ - Read `${SKILL_DIR}/references/rules/<layer>/question-manifest.json` — machine-readable structure for this layer. This is the asking checklist. It lists, per question: `group`, `required`, `maps_to`, `required_if`, `auto_derived_when`, and `options_vary_by`. Its `groups[]` carries each group's `quick_mode` flag, and `template_placeholders` is the expected-set for the Phase D self-check. Use the manifest to track coverage; use question-bank.md for the actual wording you present to the user. If a question id in one file is absent from the other, trust question-bank.md for content and note the drift.
14
36
 
15
37
  ### Phase B — Interactive Q&A
16
38
 
17
- - Ask questions one group at a time (max 3 questions per message), as defined in question-bank.md
18
- - Each question shows a "Recommended" option to reduce decision cost
19
- - **Quick mode**: ask only the groups whose `quick_mode` is `true` in `question-manifest.json`. All other groups adopt recommended defaults silently. (The manifest is the single source of truth for quick-mode membership — do not hardcode group lists here.)
20
- - **Full mode**: ask all groups in manifest order (`groups[]`).
21
- - **Conditional questions** (driven by the manifest's per-question fields):
22
- - `required_if: "<expr>"` — ask this question only when the expression over prior answers holds (e.g. Q14 `required_if: Q13 != D`). If the condition is false, the question is legitimately skipped — not a coverage gap.
23
- - `auto_derived_when: "<expr>"` — when this holds, fill the mapped placeholder(s) from the prior answer without asking (e.g. backend Q6 `auto_derived_when: Q5 in [GraphQL, gRPC]`). Count as satisfied, not skipped.
24
- - `options_vary_by: "<Qid>"` — the question is still asked; only its option list depends on that prior answer (e.g. framework/ORM options vary by language). Never affects whether the question is required.
25
- - Shortcut commands work at any point:
26
- - `recommended` / `default` -> adopt all recommended for current group
27
- - `all recommended` -> adopt all recommended for all remaining groups
28
- - `skip` -> mark current group as "Not required at this stage"
39
+ - Ask questions one group at a time (max 3 questions per message), as defined in question-bank.md.
40
+ - Each question shows a "Recommended" option to reduce decision cost.
41
+ - Quick mode: ask only the groups whose `quick_mode` is `true` in `question-manifest.json`. All other groups adopt recommended defaults silently.
42
+ - Full mode: ask all groups in manifest order (`groups[]`).
43
+ - Conditional questions:
44
+ - `required_if: "<expr>"` — ask this question only when the expression over prior answers holds. If false, the question is legitimately skipped.
45
+ - `auto_derived_when: "<expr>"` — when true, fill the mapped placeholder(s) from the prior answer without asking. Count as satisfied, not skipped.
46
+ - `options_vary_by: "<Qid>"` — the question is still asked; only its option list depends on that prior answer.
47
+ - Legacy shortcut labels in question banks such as `recommended`, `default`, or `skip` are converted into selectable options in `AskUserQuestion`.
48
+ - Do not ask the user to type these commands manually; selectable options prevent merged or missed decisions.
29
49
  - Record answers in memory after each group. Track which manifest questions are answered, auto-derived, or conditionally skipped so Phase D can verify coverage.
30
50
 
31
51
  ### Phase C — Auto-derivation
32
52
 
33
- - Read `${SKILL_DIR}/references/rules/<layer>/derivation-rules.md`
34
- - Match user answers against the trigger map using keyword matching
35
- - Derive platform-specific rules without asking the user (e.g., choosing Flutter -> auto-inject Flutter widget/persistence/navigation rules)
53
+ - Read `${SKILL_DIR}/references/rules/<layer>/derivation-rules.md`.
54
+ - Match user answers against the trigger map using keyword matching.
55
+ - Derive platform-specific rules without asking the user.
36
56
 
37
57
  ### Phase D — Render and Write
38
58
 
39
- - Read `${SKILL_DIR}/references/rules/<layer>/template.md`
40
- - Fill all template placeholders with accumulated content from Phases A+B+C
41
- - **Post-render self-check** (driven by `question-manifest.json` -> `template_placeholders`):
42
- 1. **Coverage pass** — for every placeholder in `from_questions`, confirm it traces to an answered question OR an `auto_derived_when` path OR a conditionally-skipped question (`required_if` false). If a `from_questions` placeholder has no source, you skipped a required question — go back and ask it before writing.
43
- 2. **Residual pass** — scan the rendered document for any residual `{{ ` or ` }}`; count must be 0. Placeholders in `from_fixed_rules` / `auto_generated` / `metadata` are filled from fixed-rules.md, Phase D generation, and project metadata respectively — they are NOT expected to come from Q&A, but they MUST still be rendered (no residual braces).
44
- - Generate Appendix A (Deny List) and Appendix B (Recommended Tools) per template instructions — these fill the `auto_generated` placeholders
45
- - Create `.prizmkit/rules/` directory if it doesn't exist
46
- - Write `.prizmkit/rules/<layer>-rules.md`
59
+ - Read `${SKILL_DIR}/references/rules/<layer>/template.md`.
60
+ - Fill all template placeholders with accumulated content from Phases A+B+C.
61
+ - Post-render self-check driven by `question-manifest.json` -> `template_placeholders`:
62
+ 1. Coverage pass — for every placeholder in `from_questions`, confirm it traces to an answered question OR an `auto_derived_when` path OR a conditionally-skipped question (`required_if` false). If a `from_questions` placeholder has no source, go back and ask it before writing.
63
+ 2. Residual pass — scan the rendered document for any residual `{{ ` or ` }}`; count must be 0. Placeholders in `from_fixed_rules`, `auto_generated`, and `metadata` are filled from fixed rules, Phase D generation, and project metadata.
64
+ - Generate Appendix A (Deny List) and Appendix B (Recommended Tools) per template instructions.
65
+ - Create `.prizmkit/rules/` directory if it doesn't exist.
66
+ - Write `.prizmkit/rules/<layer>-rules.md`.
67
+
68
+ ## Step 5: Update root.prizm RULES Pointer
69
+
70
+ - Ensure `.prizmkit/prizm-docs/root.prizm` has a concise `RULES:` pointer to configured rules.
71
+ - If root.prizm exists, update only the `RULES:` line.
72
+ - If root.prizm is absent, create a minimal valid root.prizm with required fields and a `RULES:` pointer.
73
+ - Do not write architecture decisions, project brief content, or changelog metadata into root.prizm.