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.
- package/bin/create-prizmkit.js +8 -6
- package/bundled/VERSION.json +3 -3
- package/bundled/adapters/codex/agent-adapter.js +38 -0
- package/bundled/adapters/codex/paths.js +27 -0
- package/bundled/adapters/codex/rules-adapter.js +30 -0
- package/bundled/adapters/codex/settings-adapter.js +27 -0
- package/bundled/adapters/codex/skill-adapter.js +65 -0
- package/bundled/adapters/codex/team-adapter.js +37 -0
- package/bundled/dev-pipeline/.env.example +2 -1
- package/bundled/dev-pipeline/README.md +10 -7
- package/bundled/dev-pipeline/lib/common.sh +278 -37
- package/bundled/dev-pipeline/run-bugfix.sh +10 -61
- package/bundled/dev-pipeline/run-feature.sh +10 -78
- package/bundled/dev-pipeline/run-recovery.sh +10 -46
- package/bundled/dev-pipeline/run-refactor.sh +10 -61
- package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +17 -7
- package/bundled/dev-pipeline/scripts/generate-bugfix-prompt.py +9 -3
- package/bundled/dev-pipeline/scripts/generate-refactor-prompt.py +9 -3
- package/bundled/dev-pipeline/scripts/utils.py +6 -4
- package/bundled/dev-pipeline-windows/.env.example +28 -0
- package/bundled/dev-pipeline-windows/README.md +30 -0
- package/bundled/dev-pipeline-windows/SCHEMA_ANALYSIS.md +525 -0
- package/bundled/dev-pipeline-windows/assets/feature-list-example.json +146 -0
- package/bundled/dev-pipeline-windows/assets/prizm-dev-team-integration.md +138 -0
- package/bundled/dev-pipeline-windows/launch-bugfix-daemon.ps1 +9 -0
- package/bundled/dev-pipeline-windows/launch-feature-daemon.ps1 +9 -0
- package/bundled/dev-pipeline-windows/launch-refactor-daemon.ps1 +9 -0
- package/bundled/dev-pipeline-windows/lib/common.ps1 +432 -0
- package/bundled/dev-pipeline-windows/lib/daemon.ps1 +140 -0
- package/bundled/dev-pipeline-windows/lib/pipeline.ps1 +446 -0
- package/bundled/dev-pipeline-windows/lib/reset.ps1 +87 -0
- package/bundled/dev-pipeline-windows/reset-bug.ps1 +9 -0
- package/bundled/dev-pipeline-windows/reset-feature.ps1 +9 -0
- package/bundled/dev-pipeline-windows/reset-refactor.ps1 +9 -0
- package/bundled/dev-pipeline-windows/run-bugfix.ps1 +9 -0
- package/bundled/dev-pipeline-windows/run-feature.ps1 +9 -0
- package/bundled/dev-pipeline-windows/run-recovery.ps1 +76 -0
- package/bundled/dev-pipeline-windows/run-refactor.ps1 +9 -0
- package/bundled/dev-pipeline-windows/scripts/check-session-status.py +228 -0
- package/bundled/dev-pipeline-windows/scripts/cleanup-logs.py +192 -0
- package/bundled/dev-pipeline-windows/scripts/detect-stuck.py +530 -0
- package/bundled/dev-pipeline-windows/scripts/generate-bootstrap-prompt.py +1737 -0
- package/bundled/dev-pipeline-windows/scripts/generate-bugfix-prompt.py +685 -0
- package/bundled/dev-pipeline-windows/scripts/generate-recovery-prompt.py +805 -0
- package/bundled/dev-pipeline-windows/scripts/generate-refactor-prompt.py +763 -0
- package/bundled/dev-pipeline-windows/scripts/init-bugfix-pipeline.py +316 -0
- package/bundled/dev-pipeline-windows/scripts/init-dev-team.py +134 -0
- package/bundled/dev-pipeline-windows/scripts/init-pipeline.py +380 -0
- package/bundled/dev-pipeline-windows/scripts/init-refactor-pipeline.py +399 -0
- package/bundled/dev-pipeline-windows/scripts/parse-stream-progress.py +388 -0
- package/bundled/dev-pipeline-windows/scripts/patch-completion-notes.py +191 -0
- package/bundled/dev-pipeline-windows/scripts/update-bug-status.py +864 -0
- package/bundled/dev-pipeline-windows/scripts/update-checkpoint.py +173 -0
- package/bundled/dev-pipeline-windows/scripts/update-feature-status.py +1501 -0
- package/bundled/dev-pipeline-windows/scripts/update-refactor-status.py +1073 -0
- package/bundled/dev-pipeline-windows/scripts/utils.py +542 -0
- package/bundled/dev-pipeline-windows/templates/agent-prompts/critic-plan-challenge.md +7 -0
- package/bundled/dev-pipeline-windows/templates/agent-prompts/dev-fix.md +7 -0
- package/bundled/dev-pipeline-windows/templates/agent-prompts/dev-implement.md +30 -0
- package/bundled/dev-pipeline-windows/templates/agent-prompts/dev-resume.md +5 -0
- package/bundled/dev-pipeline-windows/templates/agent-prompts/reviewer-review.md +7 -0
- package/bundled/dev-pipeline-windows/templates/bootstrap-prompt.md +46 -0
- package/bundled/dev-pipeline-windows/templates/bootstrap-tier1.md +43 -0
- package/bundled/dev-pipeline-windows/templates/bootstrap-tier2.md +43 -0
- package/bundled/dev-pipeline-windows/templates/bootstrap-tier3.md +43 -0
- package/bundled/dev-pipeline-windows/templates/bug-fix-list-schema.json +263 -0
- package/bundled/dev-pipeline-windows/templates/bugfix-bootstrap-prompt.md +320 -0
- package/bundled/dev-pipeline-windows/templates/feature-list-schema.json +237 -0
- package/bundled/dev-pipeline-windows/templates/refactor-bootstrap-prompt.md +331 -0
- package/bundled/dev-pipeline-windows/templates/refactor-list-schema.json +270 -0
- package/bundled/dev-pipeline-windows/templates/sections/ac-verification-checklist.md +13 -0
- package/bundled/dev-pipeline-windows/templates/sections/checkpoint-system.md +91 -0
- package/bundled/dev-pipeline-windows/templates/sections/context-budget-rules.md +33 -0
- package/bundled/dev-pipeline-windows/templates/sections/critical-paths-agent.md +10 -0
- package/bundled/dev-pipeline-windows/templates/sections/critical-paths-full.md +12 -0
- package/bundled/dev-pipeline-windows/templates/sections/critical-paths-lite.md +7 -0
- package/bundled/dev-pipeline-windows/templates/sections/directory-convention-agent.md +8 -0
- package/bundled/dev-pipeline-windows/templates/sections/directory-convention-full.md +9 -0
- package/bundled/dev-pipeline-windows/templates/sections/directory-convention-lite.md +6 -0
- package/bundled/dev-pipeline-windows/templates/sections/failure-capture.md +21 -0
- package/bundled/dev-pipeline-windows/templates/sections/feature-context.md +31 -0
- package/bundled/dev-pipeline-windows/templates/sections/phase-browser-verification-auto.md +72 -0
- package/bundled/dev-pipeline-windows/templates/sections/phase-browser-verification-opencli.md +63 -0
- package/bundled/dev-pipeline-windows/templates/sections/phase-browser-verification.md +62 -0
- package/bundled/dev-pipeline-windows/templates/sections/phase-commit-full.md +71 -0
- package/bundled/dev-pipeline-windows/templates/sections/phase-commit.md +64 -0
- package/bundled/dev-pipeline-windows/templates/sections/phase-context-snapshot-agent-suffix.md +23 -0
- package/bundled/dev-pipeline-windows/templates/sections/phase-context-snapshot-base.md +24 -0
- package/bundled/dev-pipeline-windows/templates/sections/phase-context-snapshot-lite-suffix.md +12 -0
- package/bundled/dev-pipeline-windows/templates/sections/phase-critic-plan-full.md +53 -0
- package/bundled/dev-pipeline-windows/templates/sections/phase-critic-plan.md +32 -0
- package/bundled/dev-pipeline-windows/templates/sections/phase-implement-agent.md +37 -0
- package/bundled/dev-pipeline-windows/templates/sections/phase-implement-full.md +50 -0
- package/bundled/dev-pipeline-windows/templates/sections/phase-implement-lite.md +52 -0
- package/bundled/dev-pipeline-windows/templates/sections/phase-plan-agent.md +27 -0
- package/bundled/dev-pipeline-windows/templates/sections/phase-plan-lite.md +27 -0
- package/bundled/dev-pipeline-windows/templates/sections/phase-review-agent.md +27 -0
- package/bundled/dev-pipeline-windows/templates/sections/phase-review-full.md +29 -0
- package/bundled/dev-pipeline-windows/templates/sections/phase-specify-plan-full.md +77 -0
- package/bundled/dev-pipeline-windows/templates/sections/phase0-init.md +13 -0
- package/bundled/dev-pipeline-windows/templates/sections/phase0-test-baseline.md +23 -0
- package/bundled/dev-pipeline-windows/templates/sections/session-context.md +5 -0
- package/bundled/dev-pipeline-windows/templates/sections/subagent-timeout-recovery.md +6 -0
- package/bundled/dev-pipeline-windows/templates/sections/test-failure-recovery-agent.md +67 -0
- package/bundled/dev-pipeline-windows/templates/sections/test-failure-recovery-lite.md +58 -0
- package/bundled/dev-pipeline-windows/templates/session-status-schema.json +83 -0
- package/bundled/skills/_metadata.json +1 -1
- package/bundled/skills/app-planner/SKILL.md +26 -18
- package/bundled/skills/app-planner/references/architecture-decisions.md +9 -5
- package/bundled/skills/app-planner/references/frontend-design-guide.md +1 -1
- package/bundled/skills/feature-planner/SKILL.md +9 -2
- package/bundled/skills/prizmkit-init/SKILL.md +7 -6
- package/bundled/skills/recovery-workflow/scripts/detect-recovery-state.py +2 -0
- package/bundled/skills-windows/app-planner/SKILL.md +639 -0
- package/bundled/skills-windows/app-planner/assets/app-design-guide.md +101 -0
- package/bundled/skills-windows/app-planner/references/architecture-decisions.md +52 -0
- package/bundled/skills-windows/app-planner/references/brainstorm-guide.md +101 -0
- package/bundled/skills-windows/app-planner/references/frontend-design-guide.md +71 -0
- package/bundled/skills-windows/app-planner/references/project-brief-guide.md +82 -0
- package/bundled/skills-windows/app-planner/references/red-team-checklist.md +40 -0
- package/bundled/skills-windows/app-planner/references/rules/backend/derivation-rules.md +609 -0
- package/bundled/skills-windows/app-planner/references/rules/backend/fixed-rules.md +285 -0
- package/bundled/skills-windows/app-planner/references/rules/backend/question-bank.md +249 -0
- package/bundled/skills-windows/app-planner/references/rules/backend/template.md +173 -0
- package/bundled/skills-windows/app-planner/references/rules/database/derivation-rules.md +373 -0
- package/bundled/skills-windows/app-planner/references/rules/database/fixed-rules.md +211 -0
- package/bundled/skills-windows/app-planner/references/rules/database/question-bank.md +184 -0
- package/bundled/skills-windows/app-planner/references/rules/database/template.md +158 -0
- package/bundled/skills-windows/app-planner/references/rules/frontend/derivation-rules.md +810 -0
- package/bundled/skills-windows/app-planner/references/rules/frontend/fixed-rules.md +188 -0
- package/bundled/skills-windows/app-planner/references/rules/frontend/question-bank.md +302 -0
- package/bundled/skills-windows/app-planner/references/rules/frontend/template.md +320 -0
- package/bundled/skills-windows/app-planner/references/rules/mobile/derivation-rules.md +639 -0
- package/bundled/skills-windows/app-planner/references/rules/mobile/fixed-rules.md +290 -0
- package/bundled/skills-windows/app-planner/references/rules/mobile/question-bank.md +232 -0
- package/bundled/skills-windows/app-planner/references/rules/mobile/template.md +175 -0
- package/bundled/skills-windows/bug-fix-workflow/SKILL.md +415 -0
- package/bundled/skills-windows/bug-planner/SKILL.md +395 -0
- package/bundled/skills-windows/bug-planner/assets/bug-confirmation-template.md +43 -0
- package/bundled/skills-windows/bug-planner/references/critic-and-verification.md +44 -0
- package/bundled/skills-windows/bug-planner/references/error-recovery.md +73 -0
- package/bundled/skills-windows/bug-planner/references/input-formats.md +53 -0
- package/bundled/skills-windows/bug-planner/references/schema-validation.md +25 -0
- package/bundled/skills-windows/bug-planner/references/severity-rules.md +16 -0
- package/bundled/skills-windows/bug-planner/scripts/validate-bug-list.py +322 -0
- package/bundled/skills-windows/bugfix-pipeline-launcher/SKILL.md +380 -0
- package/bundled/skills-windows/feature-pipeline-launcher/SKILL.md +441 -0
- package/bundled/skills-windows/feature-pipeline-launcher/scripts/preflight-check.py +462 -0
- package/bundled/skills-windows/feature-planner/SKILL.md +401 -0
- package/bundled/skills-windows/feature-planner/assets/evaluation-guide.md +64 -0
- package/bundled/skills-windows/feature-planner/assets/planning-guide.md +214 -0
- package/bundled/skills-windows/feature-planner/references/browser-interaction.md +59 -0
- package/bundled/skills-windows/feature-planner/references/completeness-review.md +57 -0
- package/bundled/skills-windows/feature-planner/references/decomposition-patterns.md +75 -0
- package/bundled/skills-windows/feature-planner/references/error-recovery.md +90 -0
- package/bundled/skills-windows/feature-planner/references/incremental-feature-planning.md +112 -0
- package/bundled/skills-windows/feature-planner/references/new-project-planning.md +85 -0
- package/bundled/skills-windows/feature-planner/scripts/validate-and-generate.py +1029 -0
- package/bundled/skills-windows/feature-workflow/SKILL.md +531 -0
- package/bundled/skills-windows/prizmkit-init/SKILL.md +356 -0
- package/bundled/skills-windows/prizmkit-init/assets/project-brief-template.md +82 -0
- package/bundled/skills-windows/prizmkit-init/references/config-schema.md +68 -0
- package/bundled/skills-windows/prizmkit-init/references/rules/layer-detection.md +41 -0
- package/bundled/skills-windows/prizmkit-init/references/tech-stack-catalog.md +13 -0
- package/bundled/skills-windows/prizmkit-init/references/update-supplement.md +9 -0
- package/bundled/skills-windows/recovery-workflow/SKILL.md +456 -0
- package/bundled/skills-windows/recovery-workflow/evals/evals.json +46 -0
- package/bundled/skills-windows/recovery-workflow/scripts/detect-recovery-state.py +544 -0
- package/bundled/skills-windows/refactor-pipeline-launcher/SKILL.md +406 -0
- package/bundled/skills-windows/refactor-planner/SKILL.md +540 -0
- package/bundled/skills-windows/refactor-planner/assets/planning-guide.md +292 -0
- package/bundled/skills-windows/refactor-planner/references/behavior-preservation.md +301 -0
- package/bundled/skills-windows/refactor-planner/references/refactor-scoping-guide.md +221 -0
- package/bundled/skills-windows/refactor-planner/scripts/validate-and-generate-refactor.py +858 -0
- package/bundled/skills-windows/refactor-workflow/SKILL.md +503 -0
- package/package.json +3 -2
- package/src/clean.js +73 -2
- package/src/config.js +159 -50
- package/src/detect-platform.js +16 -8
- package/src/external-skills.js +26 -19
- package/src/index.js +31 -9
- package/src/manifest.js +6 -2
- package/src/metadata.js +43 -5
- package/src/platforms.js +36 -0
- package/src/prompts.js +31 -6
- package/src/runtimes.js +20 -0
- package/src/scaffold.js +314 -110
- 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()
|