prizmkit 1.1.57 → 1.1.60

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 (188) hide show
  1. package/bin/create-prizmkit.js +8 -6
  2. package/bundled/VERSION.json +3 -3
  3. package/bundled/adapters/codex/agent-adapter.js +38 -0
  4. package/bundled/adapters/codex/paths.js +27 -0
  5. package/bundled/adapters/codex/rules-adapter.js +30 -0
  6. package/bundled/adapters/codex/settings-adapter.js +27 -0
  7. package/bundled/adapters/codex/skill-adapter.js +65 -0
  8. package/bundled/adapters/codex/team-adapter.js +37 -0
  9. package/bundled/dev-pipeline/.env.example +2 -1
  10. package/bundled/dev-pipeline/README.md +10 -7
  11. package/bundled/dev-pipeline/lib/common.sh +278 -37
  12. package/bundled/dev-pipeline/run-bugfix.sh +10 -61
  13. package/bundled/dev-pipeline/run-feature.sh +10 -78
  14. package/bundled/dev-pipeline/run-recovery.sh +10 -46
  15. package/bundled/dev-pipeline/run-refactor.sh +10 -61
  16. package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +17 -7
  17. package/bundled/dev-pipeline/scripts/generate-bugfix-prompt.py +9 -3
  18. package/bundled/dev-pipeline/scripts/generate-refactor-prompt.py +9 -3
  19. package/bundled/dev-pipeline/scripts/utils.py +6 -4
  20. package/bundled/dev-pipeline-windows/.env.example +28 -0
  21. package/bundled/dev-pipeline-windows/README.md +30 -0
  22. package/bundled/dev-pipeline-windows/SCHEMA_ANALYSIS.md +525 -0
  23. package/bundled/dev-pipeline-windows/assets/feature-list-example.json +146 -0
  24. package/bundled/dev-pipeline-windows/assets/prizm-dev-team-integration.md +138 -0
  25. package/bundled/dev-pipeline-windows/launch-bugfix-daemon.ps1 +9 -0
  26. package/bundled/dev-pipeline-windows/launch-feature-daemon.ps1 +9 -0
  27. package/bundled/dev-pipeline-windows/launch-refactor-daemon.ps1 +9 -0
  28. package/bundled/dev-pipeline-windows/lib/common.ps1 +432 -0
  29. package/bundled/dev-pipeline-windows/lib/daemon.ps1 +140 -0
  30. package/bundled/dev-pipeline-windows/lib/pipeline.ps1 +446 -0
  31. package/bundled/dev-pipeline-windows/lib/reset.ps1 +87 -0
  32. package/bundled/dev-pipeline-windows/reset-bug.ps1 +9 -0
  33. package/bundled/dev-pipeline-windows/reset-feature.ps1 +9 -0
  34. package/bundled/dev-pipeline-windows/reset-refactor.ps1 +9 -0
  35. package/bundled/dev-pipeline-windows/run-bugfix.ps1 +9 -0
  36. package/bundled/dev-pipeline-windows/run-feature.ps1 +9 -0
  37. package/bundled/dev-pipeline-windows/run-recovery.ps1 +76 -0
  38. package/bundled/dev-pipeline-windows/run-refactor.ps1 +9 -0
  39. package/bundled/dev-pipeline-windows/scripts/check-session-status.py +228 -0
  40. package/bundled/dev-pipeline-windows/scripts/cleanup-logs.py +192 -0
  41. package/bundled/dev-pipeline-windows/scripts/detect-stuck.py +530 -0
  42. package/bundled/dev-pipeline-windows/scripts/generate-bootstrap-prompt.py +1737 -0
  43. package/bundled/dev-pipeline-windows/scripts/generate-bugfix-prompt.py +685 -0
  44. package/bundled/dev-pipeline-windows/scripts/generate-recovery-prompt.py +805 -0
  45. package/bundled/dev-pipeline-windows/scripts/generate-refactor-prompt.py +763 -0
  46. package/bundled/dev-pipeline-windows/scripts/init-bugfix-pipeline.py +316 -0
  47. package/bundled/dev-pipeline-windows/scripts/init-dev-team.py +134 -0
  48. package/bundled/dev-pipeline-windows/scripts/init-pipeline.py +380 -0
  49. package/bundled/dev-pipeline-windows/scripts/init-refactor-pipeline.py +399 -0
  50. package/bundled/dev-pipeline-windows/scripts/parse-stream-progress.py +388 -0
  51. package/bundled/dev-pipeline-windows/scripts/patch-completion-notes.py +191 -0
  52. package/bundled/dev-pipeline-windows/scripts/update-bug-status.py +864 -0
  53. package/bundled/dev-pipeline-windows/scripts/update-checkpoint.py +173 -0
  54. package/bundled/dev-pipeline-windows/scripts/update-feature-status.py +1501 -0
  55. package/bundled/dev-pipeline-windows/scripts/update-refactor-status.py +1073 -0
  56. package/bundled/dev-pipeline-windows/scripts/utils.py +542 -0
  57. package/bundled/dev-pipeline-windows/templates/agent-prompts/critic-plan-challenge.md +7 -0
  58. package/bundled/dev-pipeline-windows/templates/agent-prompts/dev-fix.md +7 -0
  59. package/bundled/dev-pipeline-windows/templates/agent-prompts/dev-implement.md +30 -0
  60. package/bundled/dev-pipeline-windows/templates/agent-prompts/dev-resume.md +5 -0
  61. package/bundled/dev-pipeline-windows/templates/agent-prompts/reviewer-review.md +7 -0
  62. package/bundled/dev-pipeline-windows/templates/bootstrap-prompt.md +46 -0
  63. package/bundled/dev-pipeline-windows/templates/bootstrap-tier1.md +43 -0
  64. package/bundled/dev-pipeline-windows/templates/bootstrap-tier2.md +43 -0
  65. package/bundled/dev-pipeline-windows/templates/bootstrap-tier3.md +43 -0
  66. package/bundled/dev-pipeline-windows/templates/bug-fix-list-schema.json +263 -0
  67. package/bundled/dev-pipeline-windows/templates/bugfix-bootstrap-prompt.md +320 -0
  68. package/bundled/dev-pipeline-windows/templates/feature-list-schema.json +237 -0
  69. package/bundled/dev-pipeline-windows/templates/refactor-bootstrap-prompt.md +331 -0
  70. package/bundled/dev-pipeline-windows/templates/refactor-list-schema.json +270 -0
  71. package/bundled/dev-pipeline-windows/templates/sections/ac-verification-checklist.md +13 -0
  72. package/bundled/dev-pipeline-windows/templates/sections/checkpoint-system.md +91 -0
  73. package/bundled/dev-pipeline-windows/templates/sections/context-budget-rules.md +33 -0
  74. package/bundled/dev-pipeline-windows/templates/sections/critical-paths-agent.md +10 -0
  75. package/bundled/dev-pipeline-windows/templates/sections/critical-paths-full.md +12 -0
  76. package/bundled/dev-pipeline-windows/templates/sections/critical-paths-lite.md +7 -0
  77. package/bundled/dev-pipeline-windows/templates/sections/directory-convention-agent.md +8 -0
  78. package/bundled/dev-pipeline-windows/templates/sections/directory-convention-full.md +9 -0
  79. package/bundled/dev-pipeline-windows/templates/sections/directory-convention-lite.md +6 -0
  80. package/bundled/dev-pipeline-windows/templates/sections/failure-capture.md +21 -0
  81. package/bundled/dev-pipeline-windows/templates/sections/feature-context.md +31 -0
  82. package/bundled/dev-pipeline-windows/templates/sections/phase-browser-verification-auto.md +72 -0
  83. package/bundled/dev-pipeline-windows/templates/sections/phase-browser-verification-opencli.md +63 -0
  84. package/bundled/dev-pipeline-windows/templates/sections/phase-browser-verification.md +62 -0
  85. package/bundled/dev-pipeline-windows/templates/sections/phase-commit-full.md +71 -0
  86. package/bundled/dev-pipeline-windows/templates/sections/phase-commit.md +64 -0
  87. package/bundled/dev-pipeline-windows/templates/sections/phase-context-snapshot-agent-suffix.md +23 -0
  88. package/bundled/dev-pipeline-windows/templates/sections/phase-context-snapshot-base.md +24 -0
  89. package/bundled/dev-pipeline-windows/templates/sections/phase-context-snapshot-lite-suffix.md +12 -0
  90. package/bundled/dev-pipeline-windows/templates/sections/phase-critic-plan-full.md +53 -0
  91. package/bundled/dev-pipeline-windows/templates/sections/phase-critic-plan.md +32 -0
  92. package/bundled/dev-pipeline-windows/templates/sections/phase-implement-agent.md +37 -0
  93. package/bundled/dev-pipeline-windows/templates/sections/phase-implement-full.md +50 -0
  94. package/bundled/dev-pipeline-windows/templates/sections/phase-implement-lite.md +52 -0
  95. package/bundled/dev-pipeline-windows/templates/sections/phase-plan-agent.md +27 -0
  96. package/bundled/dev-pipeline-windows/templates/sections/phase-plan-lite.md +27 -0
  97. package/bundled/dev-pipeline-windows/templates/sections/phase-review-agent.md +27 -0
  98. package/bundled/dev-pipeline-windows/templates/sections/phase-review-full.md +29 -0
  99. package/bundled/dev-pipeline-windows/templates/sections/phase-specify-plan-full.md +77 -0
  100. package/bundled/dev-pipeline-windows/templates/sections/phase0-init.md +13 -0
  101. package/bundled/dev-pipeline-windows/templates/sections/phase0-test-baseline.md +23 -0
  102. package/bundled/dev-pipeline-windows/templates/sections/session-context.md +5 -0
  103. package/bundled/dev-pipeline-windows/templates/sections/subagent-timeout-recovery.md +6 -0
  104. package/bundled/dev-pipeline-windows/templates/sections/test-failure-recovery-agent.md +67 -0
  105. package/bundled/dev-pipeline-windows/templates/sections/test-failure-recovery-lite.md +58 -0
  106. package/bundled/dev-pipeline-windows/templates/session-status-schema.json +83 -0
  107. package/bundled/skills/_metadata.json +1 -1
  108. package/bundled/skills/app-planner/SKILL.md +26 -18
  109. package/bundled/skills/app-planner/references/architecture-decisions.md +9 -5
  110. package/bundled/skills/app-planner/references/frontend-design-guide.md +1 -1
  111. package/bundled/skills/feature-planner/SKILL.md +9 -2
  112. package/bundled/skills/prizmkit-init/SKILL.md +7 -6
  113. package/bundled/skills/recovery-workflow/scripts/detect-recovery-state.py +2 -0
  114. package/bundled/skills-windows/app-planner/SKILL.md +639 -0
  115. package/bundled/skills-windows/app-planner/assets/app-design-guide.md +101 -0
  116. package/bundled/skills-windows/app-planner/references/architecture-decisions.md +52 -0
  117. package/bundled/skills-windows/app-planner/references/brainstorm-guide.md +101 -0
  118. package/bundled/skills-windows/app-planner/references/frontend-design-guide.md +71 -0
  119. package/bundled/skills-windows/app-planner/references/project-brief-guide.md +82 -0
  120. package/bundled/skills-windows/app-planner/references/red-team-checklist.md +40 -0
  121. package/bundled/skills-windows/app-planner/references/rules/backend/derivation-rules.md +609 -0
  122. package/bundled/skills-windows/app-planner/references/rules/backend/fixed-rules.md +285 -0
  123. package/bundled/skills-windows/app-planner/references/rules/backend/question-bank.md +249 -0
  124. package/bundled/skills-windows/app-planner/references/rules/backend/template.md +173 -0
  125. package/bundled/skills-windows/app-planner/references/rules/database/derivation-rules.md +373 -0
  126. package/bundled/skills-windows/app-planner/references/rules/database/fixed-rules.md +211 -0
  127. package/bundled/skills-windows/app-planner/references/rules/database/question-bank.md +184 -0
  128. package/bundled/skills-windows/app-planner/references/rules/database/template.md +158 -0
  129. package/bundled/skills-windows/app-planner/references/rules/frontend/derivation-rules.md +810 -0
  130. package/bundled/skills-windows/app-planner/references/rules/frontend/fixed-rules.md +188 -0
  131. package/bundled/skills-windows/app-planner/references/rules/frontend/question-bank.md +302 -0
  132. package/bundled/skills-windows/app-planner/references/rules/frontend/template.md +320 -0
  133. package/bundled/skills-windows/app-planner/references/rules/mobile/derivation-rules.md +639 -0
  134. package/bundled/skills-windows/app-planner/references/rules/mobile/fixed-rules.md +290 -0
  135. package/bundled/skills-windows/app-planner/references/rules/mobile/question-bank.md +232 -0
  136. package/bundled/skills-windows/app-planner/references/rules/mobile/template.md +175 -0
  137. package/bundled/skills-windows/bug-fix-workflow/SKILL.md +415 -0
  138. package/bundled/skills-windows/bug-planner/SKILL.md +395 -0
  139. package/bundled/skills-windows/bug-planner/assets/bug-confirmation-template.md +43 -0
  140. package/bundled/skills-windows/bug-planner/references/critic-and-verification.md +44 -0
  141. package/bundled/skills-windows/bug-planner/references/error-recovery.md +73 -0
  142. package/bundled/skills-windows/bug-planner/references/input-formats.md +53 -0
  143. package/bundled/skills-windows/bug-planner/references/schema-validation.md +25 -0
  144. package/bundled/skills-windows/bug-planner/references/severity-rules.md +16 -0
  145. package/bundled/skills-windows/bug-planner/scripts/validate-bug-list.py +322 -0
  146. package/bundled/skills-windows/bugfix-pipeline-launcher/SKILL.md +380 -0
  147. package/bundled/skills-windows/feature-pipeline-launcher/SKILL.md +441 -0
  148. package/bundled/skills-windows/feature-pipeline-launcher/scripts/preflight-check.py +462 -0
  149. package/bundled/skills-windows/feature-planner/SKILL.md +401 -0
  150. package/bundled/skills-windows/feature-planner/assets/evaluation-guide.md +64 -0
  151. package/bundled/skills-windows/feature-planner/assets/planning-guide.md +214 -0
  152. package/bundled/skills-windows/feature-planner/references/browser-interaction.md +59 -0
  153. package/bundled/skills-windows/feature-planner/references/completeness-review.md +57 -0
  154. package/bundled/skills-windows/feature-planner/references/decomposition-patterns.md +75 -0
  155. package/bundled/skills-windows/feature-planner/references/error-recovery.md +90 -0
  156. package/bundled/skills-windows/feature-planner/references/incremental-feature-planning.md +112 -0
  157. package/bundled/skills-windows/feature-planner/references/new-project-planning.md +85 -0
  158. package/bundled/skills-windows/feature-planner/scripts/validate-and-generate.py +1029 -0
  159. package/bundled/skills-windows/feature-workflow/SKILL.md +531 -0
  160. package/bundled/skills-windows/prizmkit-init/SKILL.md +356 -0
  161. package/bundled/skills-windows/prizmkit-init/assets/project-brief-template.md +82 -0
  162. package/bundled/skills-windows/prizmkit-init/references/config-schema.md +68 -0
  163. package/bundled/skills-windows/prizmkit-init/references/rules/layer-detection.md +41 -0
  164. package/bundled/skills-windows/prizmkit-init/references/tech-stack-catalog.md +13 -0
  165. package/bundled/skills-windows/prizmkit-init/references/update-supplement.md +9 -0
  166. package/bundled/skills-windows/recovery-workflow/SKILL.md +456 -0
  167. package/bundled/skills-windows/recovery-workflow/evals/evals.json +46 -0
  168. package/bundled/skills-windows/recovery-workflow/scripts/detect-recovery-state.py +544 -0
  169. package/bundled/skills-windows/refactor-pipeline-launcher/SKILL.md +406 -0
  170. package/bundled/skills-windows/refactor-planner/SKILL.md +540 -0
  171. package/bundled/skills-windows/refactor-planner/assets/planning-guide.md +292 -0
  172. package/bundled/skills-windows/refactor-planner/references/behavior-preservation.md +301 -0
  173. package/bundled/skills-windows/refactor-planner/references/refactor-scoping-guide.md +221 -0
  174. package/bundled/skills-windows/refactor-planner/scripts/validate-and-generate-refactor.py +858 -0
  175. package/bundled/skills-windows/refactor-workflow/SKILL.md +503 -0
  176. package/package.json +3 -2
  177. package/src/clean.js +73 -2
  178. package/src/config.js +159 -50
  179. package/src/detect-platform.js +16 -8
  180. package/src/external-skills.js +26 -19
  181. package/src/index.js +31 -9
  182. package/src/manifest.js +6 -2
  183. package/src/metadata.js +43 -5
  184. package/src/platforms.js +36 -0
  185. package/src/prompts.js +31 -6
  186. package/src/runtimes.js +20 -0
  187. package/src/scaffold.js +314 -110
  188. package/src/upgrade.js +81 -41
@@ -0,0 +1,380 @@
1
+ #!/usr/bin/env python3
2
+ """Initialize the dev-pipeline state directory from a .prizmkit/plans/feature-list.json file.
3
+
4
+ Validates the feature list schema, checks dependency DAG for cycles,
5
+ and creates the state directory structure with pipeline and feature status files.
6
+
7
+ Usage:
8
+ python3 init-pipeline.py --feature-list <path> --state-dir <path>
9
+ """
10
+
11
+ import argparse
12
+ import json
13
+ import os
14
+ import re
15
+ import sys
16
+ from collections import deque
17
+ from datetime import datetime, timezone
18
+
19
+
20
+ EXPECTED_SCHEMA = "dev-pipeline-feature-list-v1"
21
+ FEATURE_ID_PATTERN = re.compile(r"^F-\d{3}(-[A-Z])?$")
22
+ TERMINAL_STATUSES = {"completed", "failed", "skipped", "split", "auto_skipped"}
23
+ VALID_PRIORITIES = {"critical", "high", "medium", "low"}
24
+
25
+ REQUIRED_FEATURE_FIELDS = [
26
+ "id",
27
+ "title",
28
+ "description",
29
+ "priority",
30
+ "dependencies",
31
+ "acceptance_criteria",
32
+ "status",
33
+ ]
34
+
35
+
36
+ def parse_args():
37
+ parser = argparse.ArgumentParser(
38
+ description="Initialize dev-pipeline state from a .prizmkit/plans/feature-list.json file."
39
+ )
40
+ parser.add_argument(
41
+ "--feature-list",
42
+ required=True,
43
+ help="Path to the .prizmkit/plans/feature-list.json file",
44
+ )
45
+ parser.add_argument(
46
+ "--state-dir",
47
+ required=True,
48
+ help="Path to the state directory (default: .prizmkit/state/features)",
49
+ )
50
+ return parser.parse_args()
51
+
52
+
53
+ def load_feature_list(path):
54
+ """Load and return the parsed JSON from the feature list file."""
55
+ abs_path = os.path.abspath(path)
56
+ if not os.path.isfile(abs_path):
57
+ return None, ["Feature list file not found: {}".format(abs_path)]
58
+ try:
59
+ with open(abs_path, "r", encoding="utf-8") as f:
60
+ data = json.load(f)
61
+ except json.JSONDecodeError as e:
62
+ return None, ["Invalid JSON in feature list: {}".format(str(e))]
63
+ except IOError as e:
64
+ return None, ["Cannot read feature list file: {}".format(str(e))]
65
+ return data, []
66
+
67
+
68
+ def validate_schema(data):
69
+ """Validate the top-level schema and structure of the feature list."""
70
+ errors = []
71
+
72
+ # Check $schema
73
+ schema = data.get("$schema")
74
+ if schema != EXPECTED_SCHEMA:
75
+ errors.append(
76
+ "Invalid $schema: expected '{}', got '{}'".format(EXPECTED_SCHEMA, schema)
77
+ )
78
+
79
+ # Check project_name (supports legacy app_name for backward compatibility)
80
+ project_name = data.get("project_name", data.get("app_name"))
81
+ if project_name is None:
82
+ errors.append("Missing required field: project_name")
83
+ elif not isinstance(project_name, str) or not project_name.strip():
84
+ errors.append("project_name must be a non-empty string")
85
+
86
+ # Check features array
87
+ if "features" not in data:
88
+ errors.append("Missing required field: features")
89
+ elif not isinstance(data["features"], list):
90
+ errors.append("features must be an array")
91
+
92
+ return errors
93
+
94
+
95
+ def validate_features(features):
96
+ """Validate each feature object and cross-reference dependencies."""
97
+ errors = []
98
+ feature_ids = set()
99
+ seen_ids = set()
100
+
101
+ # First pass: collect all feature IDs and validate structure
102
+ for i, feature in enumerate(features):
103
+ if not isinstance(feature, dict):
104
+ errors.append("Feature at index {} is not an object".format(i))
105
+ continue
106
+
107
+ # Check required fields
108
+ for field in REQUIRED_FEATURE_FIELDS:
109
+ if field not in feature:
110
+ errors.append(
111
+ "Feature at index {} missing required field: {}".format(i, field)
112
+ )
113
+
114
+ # Validate feature ID format
115
+ fid = feature.get("id")
116
+ if fid is not None:
117
+ if not isinstance(fid, str) or not FEATURE_ID_PATTERN.match(fid):
118
+ errors.append(
119
+ "Feature at index {} has invalid id '{}' "
120
+ "(must match F-NNN pattern)".format(i, fid)
121
+ )
122
+ elif fid in seen_ids:
123
+ errors.append("Duplicate feature id: {}".format(fid))
124
+ else:
125
+ seen_ids.add(fid)
126
+ feature_ids.add(fid)
127
+
128
+ # Validate dependencies is a list
129
+ deps = feature.get("dependencies")
130
+ if deps is not None and not isinstance(deps, list):
131
+ errors.append(
132
+ "Feature '{}' dependencies must be an array".format(
133
+ fid if fid else "index {}".format(i)
134
+ )
135
+ )
136
+
137
+ # Validate priority enum
138
+ priority = feature.get("priority")
139
+ if priority is not None and priority not in VALID_PRIORITIES:
140
+ errors.append(
141
+ "Feature '{}' has invalid priority '{}' "
142
+ "(must be one of: {})".format(
143
+ fid if fid else "index {}".format(i),
144
+ priority,
145
+ ", ".join(sorted(VALID_PRIORITIES)),
146
+ )
147
+ )
148
+
149
+ # Validate acceptance_criteria is a list
150
+ ac = feature.get("acceptance_criteria")
151
+ if ac is not None and not isinstance(ac, list):
152
+ errors.append(
153
+ "Feature '{}' acceptance_criteria must be an array".format(
154
+ fid if fid else "index {}".format(i)
155
+ )
156
+ )
157
+
158
+ # Second pass: validate dependency references
159
+ for feature in features:
160
+ if not isinstance(feature, dict):
161
+ continue
162
+ fid = feature.get("id", "unknown")
163
+ deps = feature.get("dependencies", [])
164
+ if not isinstance(deps, list):
165
+ continue
166
+ for dep in deps:
167
+ if dep not in feature_ids:
168
+ errors.append(
169
+ "Feature '{}' depends on unknown feature '{}'".format(fid, dep)
170
+ )
171
+
172
+ return errors, feature_ids
173
+
174
+
175
+ def check_dag(features):
176
+ """Check that feature dependencies form a DAG (no cycles) using topological sort.
177
+
178
+ Uses Kahn's algorithm. Returns a list of errors if cycles are detected.
179
+ """
180
+ # Build adjacency list and in-degree count
181
+ adj = {} # feature_id -> list of features that depend on it
182
+ in_degree = {}
183
+ feature_map = {}
184
+
185
+ for feature in features:
186
+ if not isinstance(feature, dict):
187
+ continue
188
+ fid = feature.get("id")
189
+ if fid is None:
190
+ continue
191
+ feature_map[fid] = feature
192
+ if fid not in adj:
193
+ adj[fid] = []
194
+ if fid not in in_degree:
195
+ in_degree[fid] = 0
196
+
197
+ for feature in features:
198
+ if not isinstance(feature, dict):
199
+ continue
200
+ fid = feature.get("id")
201
+ deps = feature.get("dependencies", [])
202
+ if not isinstance(deps, list) or fid is None:
203
+ continue
204
+ for dep in deps:
205
+ if dep in adj:
206
+ # dep -> fid (fid depends on dep, so dep must come first)
207
+ adj[dep].append(fid)
208
+ in_degree[fid] = in_degree.get(fid, 0) + 1
209
+
210
+ # Kahn's algorithm
211
+ queue = deque()
212
+ for fid in in_degree:
213
+ if in_degree[fid] == 0:
214
+ queue.append(fid)
215
+
216
+ sorted_count = 0
217
+ while queue:
218
+ node = queue.popleft()
219
+ sorted_count += 1
220
+ for neighbor in adj.get(node, []):
221
+ in_degree[neighbor] -= 1
222
+ if in_degree[neighbor] == 0:
223
+ queue.append(neighbor)
224
+
225
+ if sorted_count != len(feature_map):
226
+ # Find which features are part of cycles
227
+ cycle_members = [
228
+ fid for fid, deg in in_degree.items() if deg > 0
229
+ ]
230
+ return [
231
+ "Dependency cycle detected involving features: {}".format(
232
+ ", ".join(sorted(cycle_members))
233
+ )
234
+ ]
235
+
236
+ return []
237
+
238
+
239
+ def create_state_directory(state_dir, feature_list_path, features):
240
+ """Create the state directory structure with pipeline.json and per-feature status files."""
241
+ abs_state_dir = os.path.abspath(state_dir)
242
+ abs_feature_list_path = os.path.abspath(feature_list_path)
243
+ # Store as relative path from state_dir so pipeline.json is portable across machines
244
+ rel_feature_list_path = os.path.relpath(abs_feature_list_path, abs_state_dir)
245
+ features_dir = os.path.join(abs_state_dir, "features")
246
+
247
+ now = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
248
+ run_id = "run-" + datetime.now(timezone.utc).strftime("%Y%m%d%H%M%S")
249
+
250
+ # Create top-level state directory
251
+ os.makedirs(abs_state_dir, exist_ok=True)
252
+ os.makedirs(features_dir, exist_ok=True)
253
+
254
+ # Count features already in terminal status at init time
255
+ completed_count = sum(
256
+ 1 for f in features
257
+ if isinstance(f, dict) and f.get("status") in TERMINAL_STATUSES
258
+ )
259
+
260
+ # Write pipeline.json
261
+ pipeline_state = {
262
+ "run_id": run_id,
263
+ "status": "initialized",
264
+ "feature_list_path": rel_feature_list_path,
265
+ "created_at": now,
266
+ "total_features": len(features),
267
+ "completed_features": completed_count,
268
+ }
269
+ pipeline_path = os.path.join(abs_state_dir, "pipeline.json")
270
+ with open(pipeline_path, "w", encoding="utf-8") as f:
271
+ json.dump(pipeline_state, f, indent=2, ensure_ascii=False)
272
+ f.write("\n")
273
+
274
+ # Write per-feature status.json and create sessions directory
275
+ for feature in features:
276
+ if not isinstance(feature, dict):
277
+ continue
278
+ fid = feature.get("id")
279
+ if fid is None:
280
+ continue
281
+
282
+ feature_dir = os.path.join(features_dir, fid)
283
+ sessions_dir = os.path.join(feature_dir, "sessions")
284
+ os.makedirs(sessions_dir, exist_ok=True)
285
+
286
+ feature_status = {
287
+ "feature_id": fid,
288
+ "retry_count": 0,
289
+ "max_retries": 3,
290
+ "sessions": [],
291
+ "last_session_id": None,
292
+ "resume_from_phase": None,
293
+ "created_at": now,
294
+ "updated_at": now,
295
+ }
296
+ status_path = os.path.join(feature_dir, "status.json")
297
+ with open(status_path, "w", encoding="utf-8") as f:
298
+ json.dump(feature_status, f, indent=2, ensure_ascii=False)
299
+ f.write("\n")
300
+
301
+ return abs_state_dir
302
+
303
+
304
+ def main():
305
+ args = parse_args()
306
+
307
+ # Load feature list
308
+ data, load_errors = load_feature_list(args.feature_list)
309
+ if load_errors:
310
+ output = {"valid": False, "errors": load_errors}
311
+ print(json.dumps(output, indent=2, ensure_ascii=False))
312
+ sys.exit(1)
313
+
314
+ # Warn if feature-list.json is not under a recognizable project root.
315
+ # Walk up from the feature list directory to find a project root indicator.
316
+ feature_list_dir = os.path.dirname(os.path.abspath(args.feature_list))
317
+ indicators = ['.git', 'package.json', '.prizmkit']
318
+
319
+ def _find_project_root(start_dir):
320
+ d = start_dir
321
+ while True:
322
+ if any(os.path.exists(os.path.join(d, ind)) for ind in indicators):
323
+ return d
324
+ parent = os.path.dirname(d)
325
+ if parent == d:
326
+ return None
327
+ d = parent
328
+
329
+ project_root = _find_project_root(feature_list_dir)
330
+ if project_root is None:
331
+ sys.stderr.write(
332
+ "Warning: Could not find project root (no .git, package.json, or .prizmkit) above: {}\n".format(
333
+ feature_list_dir
334
+ )
335
+ )
336
+
337
+ # Validate schema
338
+ schema_errors = validate_schema(data)
339
+ if schema_errors:
340
+ output = {"valid": False, "errors": schema_errors}
341
+ print(json.dumps(output, indent=2, ensure_ascii=False))
342
+ sys.exit(1)
343
+
344
+ # Validate features
345
+ features = data["features"]
346
+ feature_errors, feature_ids = validate_features(features)
347
+ if feature_errors:
348
+ output = {"valid": False, "errors": feature_errors}
349
+ print(json.dumps(output, indent=2, ensure_ascii=False))
350
+ sys.exit(1)
351
+
352
+ # Check DAG (no cycles)
353
+ dag_errors = check_dag(features)
354
+ if dag_errors:
355
+ output = {"valid": False, "errors": dag_errors}
356
+ print(json.dumps(output, indent=2, ensure_ascii=False))
357
+ sys.exit(1)
358
+
359
+ # Create state directory
360
+ try:
361
+ abs_state_dir = create_state_directory(
362
+ args.state_dir, args.feature_list, features
363
+ )
364
+ except (IOError, OSError) as e:
365
+ output = {"valid": False, "errors": ["Failed to create state directory: {}".format(str(e))]}
366
+ print(json.dumps(output, indent=2, ensure_ascii=False))
367
+ sys.exit(1)
368
+
369
+ # Success output
370
+ output = {
371
+ "valid": True,
372
+ "features_count": len(features),
373
+ "state_dir": abs_state_dir,
374
+ }
375
+ print(json.dumps(output, indent=2, ensure_ascii=False))
376
+ sys.exit(0)
377
+
378
+
379
+ if __name__ == "__main__":
380
+ main()