@friedbotstudio/create-baseline 0.5.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -10
- package/bin/cli.js +46 -15
- package/obj/template/.claude/commands/init-project-doctor.md +74 -0
- package/obj/template/.claude/hooks/lib/resume_writer.py +14 -1
- package/obj/template/.claude/hooks/track_guard.sh +11 -1
- package/obj/template/.claude/manifest.json +848 -230
- package/obj/template/.claude/schemas/workflow-track.v1.json +64 -0
- package/obj/template/.claude/skills/audit-baseline/audit.sh +6 -3
- package/obj/template/.claude/skills/chore/SKILL.md +2 -2
- package/obj/template/.claude/skills/harness/SKILL.md +15 -6
- package/obj/template/.claude/skills/intake/SKILL.md +1 -1
- package/obj/template/.claude/skills/swarm-plan/SKILL.md +2 -0
- package/obj/template/.claude/skills/tdd/SKILL.md +2 -2
- package/obj/template/.claude/skills/triage/SKILL.md +29 -6
- package/obj/template/.claude/skills/triage/seed-tasklist.mjs +107 -0
- package/obj/template/.claude/skills/upgrade-project/SKILL.md +121 -0
- package/obj/template/.claude/workflows.jsonl +6 -0
- package/obj/template/CLAUDE.md +14 -19
- package/obj/template/docs/init/seed.md +152 -7
- package/package.json +1 -1
- package/src/.claude/workflows.template.jsonl +6 -0
- package/src/CLAUDE.template.md +14 -19
- package/src/cli/diff-render.js +54 -0
- package/src/cli/install.js +38 -3
- package/src/cli/manifest.js +7 -3
- package/src/cli/merge.js +107 -13
- package/src/cli/track-tasklist-materializer.js +223 -0
- package/src/cli/tui/upgrade.js +130 -27
- package/src/cli/upgrade-tiers.js +256 -0
- package/src/cli/workflow-migrator.js +40 -0
- package/src/cli/workflows-validator-invariants.js +417 -0
- package/src/cli/workflows-validator-predicates.js +19 -0
- package/src/cli/workflows-validator.js +156 -0
- package/src/seed.template.md +152 -7
- package/obj/template/.claude/skills/google-analytics/SKILL.md +0 -129
- package/obj/template/.claude/skills/google-analytics/references/audiences.md +0 -389
- package/obj/template/.claude/skills/google-analytics/references/bigquery.md +0 -470
- package/obj/template/.claude/skills/google-analytics/references/custom-dimensions.md +0 -355
- package/obj/template/.claude/skills/google-analytics/references/custom-events.md +0 -383
- package/obj/template/.claude/skills/google-analytics/references/data-management.md +0 -416
- package/obj/template/.claude/skills/google-analytics/references/debugview.md +0 -364
- package/obj/template/.claude/skills/google-analytics/references/events-fundamentals.md +0 -398
- package/obj/template/.claude/skills/google-analytics/references/gtag.md +0 -502
- package/obj/template/.claude/skills/google-analytics/references/gtm-integration.md +0 -483
- package/obj/template/.claude/skills/google-analytics/references/measurement-protocol.md +0 -519
- package/obj/template/.claude/skills/google-analytics/references/privacy.md +0 -441
- package/obj/template/.claude/skills/google-analytics/references/recommended-events.md +0 -464
- package/obj/template/.claude/skills/google-analytics/references/reporting.md +0 -397
- package/obj/template/.claude/skills/google-analytics/references/setup.md +0 -344
- package/obj/template/.claude/skills/google-analytics/references/user-tracking.md +0 -417
- package/obj/template/.claude/skills/optimize-seo/SKILL.md +0 -313
- package/obj/template/.claude/skills/optimize-seo/scripts/pagespeed.mjs +0 -197
- package/obj/template/.claude/skills/pagespeed-insights/LICENSE.md +0 -37
- package/obj/template/.claude/skills/pagespeed-insights/SKILL.md +0 -446
- package/obj/template/.claude/skills/pagespeed-insights/reference.md +0 -50
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "./workflow-track.v1.json",
|
|
4
|
+
"title": "Workflow Track v1",
|
|
5
|
+
"description": "One Track record per line in .claude/workflows.jsonl. The Article IV invariants I1..I11 are enforced at runtime by src/cli/workflows-validator.js, NOT by JSON Schema alone — schema validation is structural; invariant validation is semantic. See docs/init/seed.md §17 for the full semantic contract.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": ["$schema", "track_id", "name", "description", "selectable", "selector_hints", "preconditions", "invariants", "nodes"],
|
|
8
|
+
"additionalProperties": false,
|
|
9
|
+
"properties": {
|
|
10
|
+
"$schema": { "type": "string" },
|
|
11
|
+
"track_id": { "type": "string", "minLength": 1 },
|
|
12
|
+
"name": { "type": "string", "minLength": 1 },
|
|
13
|
+
"description": { "type": "string" },
|
|
14
|
+
"selectable": { "type": "boolean" },
|
|
15
|
+
"selector_hints": { "type": "array", "items": { "type": "string" } },
|
|
16
|
+
"preconditions": { "type": "array", "items": { "$ref": "#/$defs/Predicate" } },
|
|
17
|
+
"invariants": { "type": "array", "items": { "enum": ["commits", "requires_spec", "requires_swarm", "chore", "git_only"] } },
|
|
18
|
+
"nodes": { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/Node" } }
|
|
19
|
+
},
|
|
20
|
+
"$defs": {
|
|
21
|
+
"Predicate": {
|
|
22
|
+
"type": "object",
|
|
23
|
+
"required": ["name"],
|
|
24
|
+
"additionalProperties": false,
|
|
25
|
+
"properties": {
|
|
26
|
+
"name": { "enum": ["requires_git", "requires_user_override", "requires_min_components", "requires_phase_completed", "requires_skill_present"] },
|
|
27
|
+
"argument": { "type": "string" }
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"Node": {
|
|
31
|
+
"type": "object",
|
|
32
|
+
"required": ["id", "type", "depends_on", "blocks", "can_parallel", "needs_user"],
|
|
33
|
+
"additionalProperties": false,
|
|
34
|
+
"properties": {
|
|
35
|
+
"id": { "type": "string", "minLength": 1 },
|
|
36
|
+
"type": { "enum": ["task", "selector"] },
|
|
37
|
+
"skill": { "type": "string" },
|
|
38
|
+
"sub_track": { "type": "string" },
|
|
39
|
+
"alternates": { "type": "array", "items": { "$ref": "#/$defs/Alternate" } },
|
|
40
|
+
"input": { "type": "string" },
|
|
41
|
+
"invocation_prompt": { "type": "string" },
|
|
42
|
+
"output": { "type": "string" },
|
|
43
|
+
"output_formatter_prompt": { "type": "string" },
|
|
44
|
+
"depends_on": { "type": "array", "items": { "type": "string" } },
|
|
45
|
+
"blocks": { "type": "array", "items": { "type": "string" } },
|
|
46
|
+
"can_parallel": { "type": "boolean" },
|
|
47
|
+
"needs_user": { "type": "boolean" },
|
|
48
|
+
"activeForm": { "type": "string" },
|
|
49
|
+
"metadata": { "type": "object" }
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
"Alternate": {
|
|
53
|
+
"type": "object",
|
|
54
|
+
"required": ["preconditions"],
|
|
55
|
+
"additionalProperties": false,
|
|
56
|
+
"properties": {
|
|
57
|
+
"skill": { "type": "string" },
|
|
58
|
+
"sub_track": { "type": "string" },
|
|
59
|
+
"preconditions": { "type": "array", "items": { "$ref": "#/$defs/Predicate" } },
|
|
60
|
+
"description": { "type": "string" }
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -80,7 +80,7 @@ def read_skill_owner(slug):
|
|
|
80
80
|
m = re.search(r'^owner:\s*(\S+)\s*$', fm.group(1), re.MULTILINE)
|
|
81
81
|
return m.group(1) if m else None
|
|
82
82
|
|
|
83
|
-
EXPECTED_COMMANDS = {"approve-spec", "approve-swarm", "grant-commit", "grant-push", "init-project"}
|
|
83
|
+
EXPECTED_COMMANDS = {"approve-spec", "approve-swarm", "grant-commit", "grant-push", "init-project", "init-project-doctor"}
|
|
84
84
|
|
|
85
85
|
EXPECTED_MEMORY_FILES = {
|
|
86
86
|
# Canonical files (seven)
|
|
@@ -186,7 +186,7 @@ skills_claimed = find_count(
|
|
|
186
186
|
r"\b(\d+|twenty-(?:four|five|six|seven|eight|nine)|"
|
|
187
187
|
r"thirty|thirty-(?:one|two|three|four|five|six|seven|eight|nine)|forty)\s+skills?\b")
|
|
188
188
|
gates_claimed = find_count(r"\b(\d+|three)\s+consent\s+gates?\b")
|
|
189
|
-
cmds_claimed = 5 if re.search(r"four\s+consent\s+gates?\s*\+\s*one\s+bootstrap", seed, re.IGNORECASE) else None
|
|
189
|
+
cmds_claimed = 6 if re.search(r"four\s+consent\s+gates?\s*\+\s*one\s+bootstrap\s*\+\s*one\s+doctor", seed, re.IGNORECASE) else (5 if re.search(r"four\s+consent\s+gates?\s*\+\s*one\s+bootstrap", seed, re.IGNORECASE) else None)
|
|
190
190
|
|
|
191
191
|
def check_count(label, claimed, actual):
|
|
192
192
|
if claimed is None:
|
|
@@ -261,13 +261,16 @@ def check_skill_ownership():
|
|
|
261
261
|
if not skill_dir.is_dir():
|
|
262
262
|
add(f"skill ownership: {slug}", "FAIL", "baseline skill missing")
|
|
263
263
|
continue
|
|
264
|
-
for path,
|
|
264
|
+
for path, entry in files_map.items():
|
|
265
265
|
if not path.startswith(f".claude/skills/{slug}/"):
|
|
266
266
|
continue
|
|
267
267
|
disk_file = root / path
|
|
268
268
|
if not disk_file.exists():
|
|
269
269
|
add(f"skill ownership: {slug}", "FAIL", f"baseline skill missing: {path}")
|
|
270
270
|
continue
|
|
271
|
+
# Manifest v3+ wraps each entry as {sha256, tier}; v2 ships bare
|
|
272
|
+
# sha strings. Both shapes are accepted at read time.
|
|
273
|
+
expected_hash = entry if isinstance(entry, str) else (entry or {}).get("sha256")
|
|
271
274
|
actual = hashlib.sha256(disk_file.read_bytes()).hexdigest()
|
|
272
275
|
if actual != expected_hash:
|
|
273
276
|
add(f"skill ownership: {slug}", "FAIL", f"hash mismatch at {path}")
|
|
@@ -35,7 +35,7 @@ The classification rule is: *if there is no failing test that should exist for t
|
|
|
35
35
|
|
|
36
36
|
## Prereq
|
|
37
37
|
|
|
38
|
-
`.claude/state/workflow.json` exists with `
|
|
38
|
+
`.claude/state/workflow.json` exists with `track_id == "chore"` (post-§18; legacy `entry_phase == "chore"` accepted for pre-§18 in-flight workflows). If the prereq is not met, refuse and surface the mismatch.
|
|
39
39
|
|
|
40
40
|
## Phase shape
|
|
41
41
|
|
|
@@ -74,7 +74,7 @@ If a conditional phase is required, run it **before** `/grant-commit`. If you sk
|
|
|
74
74
|
|
|
75
75
|
## Steps
|
|
76
76
|
|
|
77
|
-
1. Read `.claude/state/workflow.json`. Confirm `entry_phase == "chore"
|
|
77
|
+
1. Read `.claude/state/workflow.json`. Confirm `track_id == "chore"` (post-§18) OR `entry_phase == "chore"` (legacy pre-§18). If neither, stop and surface the mismatch — the user reached this skill without the correct triage classification.
|
|
78
78
|
2. Restate the intended edits inline: file paths, brief description per file, estimated total diff size. Confirm with the user if anything is ambiguous.
|
|
79
79
|
3. Apply the edits via `Edit` / `MultiEdit` / `Write`. Honour the engineering rules from CLAUDE.md Article VI (no stubs, no commented-out code, no `TODO` / `FIXME` / `HACK` / `XXX`).
|
|
80
80
|
4. **Run the binding test command and stamp the verdict (inlined verify).** Per `.claude/skills/verify/SKILL.md` (the contract doc): read `.claude/project.json → test.cmd`; run via Bash from project root (capture stdout, stderr, exit code; no retry); apply verdict rules (`PASS` iff exit 0 AND at least one test executed AND no failed/errored test; otherwise `FAIL`); atomically write `.claude/state/last_test_result` with the canonical four-line format. The `verify_pass_guard` hook reads line 1 as the binding verdict. If the verdict is `FAIL`, stop — the user investigates; chore does not loop. Write `.claude/state/harness_state` with `state: "yielded"` and `reason: "chore verify FAIL"` so the Stop hook stays silent.
|
|
@@ -56,6 +56,7 @@ THEN write `harness_state`. The marker is the session-scoped "in the loop" signa
|
|
|
56
56
|
3. **Fresh start or resume?**
|
|
57
57
|
- `.claude/state/workflow.json` exists → **resume**.
|
|
58
58
|
- Absent → **fresh start**. The argument (or surrounding conversation) is the request; proceed to Pillar 1.
|
|
59
|
+
3a. **Pre-§18 workflow.json migrator (post-§18 baseline).** If `workflow.json` carries the pre-§18 shape (has `entry_phase`, no `track_id`), run a one-shot migrator before continuing: `node -e "import('./src/cli/workflow-migrator.js').then(m => m.migrateWorkflowJsonInPlace('.claude/state/workflow.json'))"`. The migrator derives `track_id` from `entry_phase` via the canonical map (intake → intake-full, spec → spec-entry, tdd → tdd-quickfix, chore → chore), remaps `completed[]` phase-names to node-ids, initializes `skipped_alternates: []`, refreshes `updated_at`, and removes `entry_phase`. Idempotent: already-post-§18 input is a no-op. Unmapped `entry_phase` throws; halt with the migrator's error message and tell the user to re-run `/triage` to restart this workflow.
|
|
59
60
|
4. **Ground the user before acting.** When `_resume.md` is present, open with one sentence summarizing where things stood. Grounding only — do not invent state not in `workflow.json`.
|
|
60
61
|
5. **Detect divergence.** If `_resume.md`'s recent prompts contradict `workflow.json` (e.g., the user said "actually skip security" mid-session and `exceptions` doesn't reflect it), do **not** auto-proceed. Surface as a clarifying question. Memory accelerates triage; it never authorizes a skip.
|
|
61
62
|
6. **Arm the safety net.** Marker FIRST: `echo "<slug>" > .claude/state/.harness_active`. Then write `harness_state` with `{state: "continue", slug, reason: "loop armed; preflight passed"}`. This pair stays in place for the entire loop; mid-loop crashes are now covered by the Stop hook.
|
|
@@ -66,8 +67,8 @@ Log every transition to `.claude/state/harness/<slug>.log` with timestamp + `ent
|
|
|
66
67
|
|
|
67
68
|
Inside each iteration:
|
|
68
69
|
|
|
69
|
-
1. **TaskList.** If empty (first invocation in a fresh session, or session-bound state was reset), **re-seed
|
|
70
|
-
2. **Pick the next action.** Find the lowest-id `pending` task whose `blockedBy` list is empty.
|
|
70
|
+
1. **TaskList.** If empty (first invocation in a fresh session, or session-bound state was reset), **re-seed** from `workflow.json → track_id` (post-§18) via the materializer: run `node .claude/skills/triage/seed-tasklist.mjs <track_id> <slug>` to emit the canonical TaskList JSON for the track. Skip nodes whose `metadata.phase` is in `workflow.json → completed` or in `exceptions`. Wire `addBlockedBy` from each emitted entry's `blockedBy` ordinals (translated to session task_ids of predecessors). Pre-§18 workflow.json files (`entry_phase` set, no `track_id`) SHALL have been migrated by preflight Step 3a before re-seed runs; if `track_id` is still absent here, fall back to the canonical templates documented in `triage/SKILL.md` Step 5's "Reference: canonical track shapes" subsection.
|
|
71
|
+
2. **Pick the next action.** Find the lowest-id `pending` task whose `blockedBy` list is empty. Then check for a **parallel cluster** (SP-002 / Article IV invariant supporting `can_parallel: true`): if the picked task carries `can_parallel: true` in its metadata AND one or more SIBLING pending tasks share both (a) identical `blockedBy` lists AND (b) `can_parallel: true`, group the picked task + every such sibling into a single cluster for this iteration. If no siblings share both conditions, proceed with the single task as today.
|
|
71
72
|
3. **If no pending task remains** (workflow complete), **EXIT LOOP with DONE**:
|
|
72
73
|
- Marker FIRST: `rm -f .claude/state/.harness_active`.
|
|
73
74
|
- Write `harness_state` with `{state: "done", slug, reason: "workflow complete"}`.
|
|
@@ -76,10 +77,18 @@ Inside each iteration:
|
|
|
76
77
|
- Marker FIRST: `rm -f .claude/state/.harness_active`.
|
|
77
78
|
- Write `harness_state` with `{state: "yielded", slug, reason: "yielded at /<gate>"}` — exactly three fields.
|
|
78
79
|
- Break out of the loop; the terminal message names the consent command for the user to run.
|
|
79
|
-
5. **Otherwise INVOKE the phase skill:**
|
|
80
|
-
-
|
|
81
|
-
|
|
82
|
-
|
|
80
|
+
5. **Otherwise INVOKE the phase skill(s):**
|
|
81
|
+
- **Single-task path** (no parallel cluster detected at step 2):
|
|
82
|
+
- `TaskUpdate` to `in_progress` (set `activeForm` to the imperative-progressive form, e.g. "Running scout").
|
|
83
|
+
- Log `entered <phase>` to `.claude/state/harness/<slug>.log`.
|
|
84
|
+
- Invoke the matching phase skill via the **`Skill` tool — one invocation per loop iteration**.
|
|
85
|
+
- **Parallel-cluster path** (cluster of ≥2 tasks all with `can_parallel: true` + identical blockedBy detected at step 2):
|
|
86
|
+
- `TaskUpdate` every cluster task to `in_progress`.
|
|
87
|
+
- Log `entered cluster: <ids>` to the harness log.
|
|
88
|
+
- Dispatch the cluster via the `Task` tool, one `Task` invocation per cluster member — typically `swarm-worker` with a recipe per node, but the `skill:` field on each Node decides the worker target. All `Task` invocations go in a SINGLE assistant message so the runtime dispatches them concurrently.
|
|
89
|
+
- Wait for all cluster members to return.
|
|
90
|
+
- On all-success: mark each cluster task `completed`; refresh the marker + state once (NOT per-cluster-member); continue loop.
|
|
91
|
+
- On any cluster member's failure: EXIT LOOP with YIELD; `reason: "cluster <ids>: <failed-id> failed: <summary>"`. Leave succeeded members `completed` and the failed member `in_progress` for inspection.
|
|
83
92
|
- On phase-skill success:
|
|
84
93
|
- `TaskUpdate` to `completed`.
|
|
85
94
|
- Append the phase name to `workflow.json → completed`; update `updated_at`.
|
|
@@ -10,7 +10,7 @@ You are drafting an **intake document** — the earliest structured artifact in
|
|
|
10
10
|
|
|
11
11
|
## Prerequisite
|
|
12
12
|
|
|
13
|
-
`.claude/state/workflow.json` exists (written by `/triage`) with `
|
|
13
|
+
`.claude/state/workflow.json` exists (written by `/triage`) with EITHER `track_id == "intake-full"` (post-§18 canonical) OR `entry_phase == "intake"` (legacy pre-§18 — accepted for in-flight workflows the harness preflight migrator hasn't run on yet) — OR a later phase that lists `intake` in `exceptions`. If neither is true, stop and instruct the user to run `/triage` first.
|
|
14
14
|
|
|
15
15
|
## Inputs
|
|
16
16
|
|
|
@@ -8,6 +8,8 @@ description: Decompose an approved spec into a dependency-ordered swarm plan —
|
|
|
8
8
|
|
|
9
9
|
Invoked after `/approve-spec` and before `/tdd` on any spec that has ≥3 independent components worth parallelizing (per `project.json → swarm.min_tasks_worth_swarming`). For smaller specs, skip swarm and go straight to `/tdd` solo.
|
|
10
10
|
|
|
11
|
+
**Post-§18 amendment.** Beyond the canonical `.claude/state/swarm/<slug>.json` plan, swarm-plan ALSO emits a runtime sub-track overlay at `.claude/state/swarm/<slug>.jsonl` (JSONL) when the chosen workflow track contains a selector node whose `swarm-implementation` alternate is chosen. The overlay's single line is a transient Track record with `selectable: false`, `track_id: "swarm-runtime-<slug>"`, and `nodes[]` enumerating the swarm-worker dispatches as `can_parallel: true` peers. The harness reloads runtime overlays from `.claude/state/swarm/*.jsonl` at the same time it reads `.claude/workflows.jsonl`, so the runtime Track set is the union. The overlay is deleted by `/archive` along with the rest of the workflow's swarm state.
|
|
12
|
+
|
|
11
13
|
## Prereqs
|
|
12
14
|
|
|
13
15
|
1. `.claude/state/spec_approvals/<slug>.approval` exists (spec is human-approved).
|
|
@@ -13,7 +13,7 @@ Main-context decisions live here. Worker execution happens in harness ticks.
|
|
|
13
13
|
|
|
14
14
|
# Prereq
|
|
15
15
|
|
|
16
|
-
Either an approved spec exists (`.claude/state/spec_approvals/<slug>.approval`) OR `
|
|
16
|
+
Either an approved spec exists (`.claude/state/spec_approvals/<slug>.approval`) OR `track_id` in `workflow.json` is `tdd-quickfix` (post-§18; legacy `entry_phase == "tdd"` accepted on pre-§18 workflow.json files the harness preflight migrator hasn't run on yet — quickfix/bugfix direct-to-TDD route).
|
|
17
17
|
|
|
18
18
|
If neither, stop and direct the user to `/triage` first.
|
|
19
19
|
|
|
@@ -21,7 +21,7 @@ If neither, stop and direct the user to `/triage` first.
|
|
|
21
21
|
|
|
22
22
|
## 1. Verify prereq
|
|
23
23
|
|
|
24
|
-
Read `.claude/state/workflow.json`. Confirm `tdd` is the current phase to run (entry_phase
|
|
24
|
+
Read `.claude/state/workflow.json`. Confirm `tdd` is the current phase to run: `track_id == "tdd-quickfix"` for quickfix (post-§18; legacy `entry_phase == "tdd"` also accepted), OR `spec` is in `completed` AND the spec approval token exists for spec-entry / intake-full tracks.
|
|
25
25
|
|
|
26
26
|
## 2. Decide the scenario recipe (in main context)
|
|
27
27
|
|
|
@@ -19,24 +19,47 @@ Triage the user's request and set up `.claude/state/workflow.json` so downstream
|
|
|
19
19
|
1. Restate the request back to the user in 1-2 sentences, and name the entry phase you've chosen and why.
|
|
20
20
|
2. **Git-repo detection (mandatory).** Run `git rev-parse --is-inside-work-tree 2>/dev/null` at the project root. If the exit status is non-zero, the project is not a git repository: gate C / `commit` are inapplicable AND the swarm path is unavailable because worktree isolation (the swarm contract's physical safety mechanism) requires git (CLAUDE.md Article IV "Phase 6c and Phase 11 are git-conditional", Article VII). Append `"swarm-plan"`, `"approve-swarm"`, `"swarm-dispatch"`, `"grant-commit"`, `"changelog"`, and `"commit"` to the exceptions array you'll write in step 4. `"changelog"` is auto-excepted alongside `"commit"` because Phase 11.5 is a pre-commit curator with no purpose outside a commit-bearing workflow. Tell the user: "Non-git project detected — `swarm-plan`, `approve-swarm`, `swarm-dispatch`, `grant-commit`, `changelog`, and `commit` auto-excepted. Phase 6 routes to solo `/tdd`. Workflow ends after `/archive`. Persistence outside git is your responsibility."
|
|
21
21
|
3. If the user has not confirmed yet, ask: "Entry phase = <X>. Exceptions = <Y>. Proceed? (or tell me a different entry)"
|
|
22
|
-
4. On confirmation, write `.claude/state/workflow.json
|
|
22
|
+
4. On confirmation, write `.claude/state/workflow.json` (post-§18 shape — uses `track_id` from the chosen Track in `.claude/workflows.jsonl`, NOT the old `entry_phase` field):
|
|
23
23
|
```json
|
|
24
24
|
{
|
|
25
25
|
"request": "<the request>",
|
|
26
26
|
"slug": "<workflow slug>",
|
|
27
|
-
"
|
|
27
|
+
"track_id": "<intake-full|spec-entry|tdd-quickfix|chore>",
|
|
28
28
|
"exceptions": ["<phase>", ...],
|
|
29
29
|
"completed": [],
|
|
30
|
+
"skipped_alternates": [],
|
|
30
31
|
"source_backlog_keys": ["<backlog stable key>", ...],
|
|
31
32
|
"created_at": <epoch>,
|
|
32
33
|
"updated_at": <epoch>
|
|
33
34
|
}
|
|
34
35
|
```
|
|
35
36
|
|
|
37
|
+
The `track_id` value is the `track_id` field of the Track you picked in step 5c above (one of `intake-full`, `spec-entry`, `tdd-quickfix`, `chore`, OR a project-declared selectable Track from `.claude/workflows.jsonl`). The legacy pre-§18 field `entry_phase` is NOT written — downstream skills (intake / tdd / chore / harness) read `track_id` directly. Pre-§18 workflow.json files (those that still carry `entry_phase`) are auto-migrated by harness preflight Step 3a via `src/cli/workflow-migrator.js`.
|
|
38
|
+
|
|
36
39
|
The `source_backlog_keys` field is optional. When the user's request explicitly names one or more backlog entries this workflow picks up (the common framing is a `Source:` line listing backlog keys), populate the array with those keys. `/commit` (Phase 11) reads this field and invokes `sweep.py --mode stamp-closure` after the commit lands, stamping each named entry with `status: picked-up` + `superseded-at: <today>` so the next `/memory-flush` Step 0a auto-closes them. Absent / empty array → `/commit` skips the stamp step entirely (backward-compatible for any workflow that pre-dates the field). `/triage` does NOT auto-detect backlog keys from free-form prose — the user populates the field (or names them in the triage prompt and you populate it during step 4).
|
|
37
|
-
5. **Seed the workflow tasklist
|
|
40
|
+
5. **Seed the workflow tasklist** — workflows.jsonl-driven (post-§18; per CLAUDE.md Article IV amendment + seed.md §18).
|
|
41
|
+
|
|
42
|
+
**Source of truth.** `.claude/workflows.jsonl` declares every Track this project can execute, one Track per JSONL line. The four canonical tracks (intake-full, spec-entry, tdd-quickfix, chore) plus any per-project additions live there. Sub-tracks (selectable=false; e.g., swarm-implementation, tdd-worker-chain) are referenced by `sub_track:` in selector-node alternates.
|
|
43
|
+
|
|
44
|
+
**Procedure:**
|
|
45
|
+
|
|
46
|
+
a. **Load + validate.** Run `node .claude/skills/triage/seed-tasklist.mjs --validate-only` to parse `.claude/workflows.jsonl` and verify every Track against the §18 invariants (I1..I11). On validation failure, the helper exits non-zero and prints a named error citing the offending track / node / line. Halt triage; tell the user to fix `workflows.jsonl` or run `/init-project doctor` to repair drift.
|
|
47
|
+
|
|
48
|
+
b. **Classify (LLM-driven).** Read each *selectable* Track's `name`, `description`, and `selector_hints` from `workflows.jsonl`. Match against the user's request using natural-language reasoning — selector_hints are descriptive aids, NOT match tokens. Rank the tracks by plausibility for the request. Selectable Tracks whose track-level `preconditions[]` evaluate false in this project (e.g., `requires_git` on a non-git project) are excluded from the candidate set BEFORE ranking — they cannot be picked.
|
|
49
|
+
|
|
50
|
+
c. **Confirm (AskUserQuestion, always).** Present the picked Track plus the top 2-3 alternates via `AskUserQuestion`. Confidence thresholds are not used; the user picks. On ambiguity (e.g., chore vs intake-full for a documentation refactor), surface both and let the user decide.
|
|
51
|
+
|
|
52
|
+
d. **Materialize TaskList.** Run `node .claude/skills/triage/seed-tasklist.mjs <track_id> <slug>` to emit the canonical TaskList JSON for the chosen Track (subjects, activeForms, metadata.phase, needs_user, blockedBy by ordinal — driven by `src/cli/track-tasklist-materializer.js`). For each entry, call `TaskCreate` to register the task; capture the returned task_id. For each entry's `blockedBy` ordinals, call `TaskUpdate addBlockedBy` mapping ordinals to the captured task_ids of the predecessor entries.
|
|
53
|
+
|
|
54
|
+
e. **`source_backlog_keys` (optional).** If the user's request names backlog entries (typical framing: a `Source:` line listing backlog keys), populate `workflow.json → source_backlog_keys` with those keys. `/commit` reads this and stamps closure on the named entries after the commit lands.
|
|
55
|
+
|
|
56
|
+
**Fallback for missing workflows.jsonl.** A baseline install always ships `.claude/workflows.jsonl` (pristine template overlaid by `scripts/build-template.sh` Stage 2; CLI install copies it). If the file is missing on disk, the install is broken — halt triage with a named error and tell the user to run `/init-project doctor` to regenerate the file from the pristine template.
|
|
57
|
+
|
|
58
|
+
**Non-git projects.** Tracks declaring `git_only` invariant (e.g., `swarm-implementation`) are excluded from the candidate set on non-git projects. The `commit`-bearing tracks (intake-full, spec-entry, tdd-quickfix, chore) auto-except their `grant-commit`, `changelog`, `commit` nodes — the materializer's runtime context (passed by triage) carries an `excluded_node_ids` set; the helper skips those nodes during TaskCreate emission.
|
|
59
|
+
|
|
60
|
+
**Reference: canonical track shapes (mirrored in workflows.jsonl).** The four selectable tracks shipped in the pristine template are byte-equivalent to these pre-§18 ordering descriptions:
|
|
38
61
|
|
|
39
|
-
**For `chore` track** (single phase + memory-flush +
|
|
62
|
+
**For `chore` track** (single phase + memory-flush + commit gate):
|
|
40
63
|
- `Run /chore for <slug>` — activeForm: "Running chore", metadata: `{"phase": "chore"}`
|
|
41
64
|
- `Run /memory-flush for <slug>` — activeForm: "Running memory-flush", metadata: `{"phase": "memory-flush"}`, addBlockedBy previous
|
|
42
65
|
- `Wait for /grant-commit` — metadata: `{"phase": "grant-commit", "needs_user": true}`, addBlockedBy previous
|
|
@@ -48,7 +71,7 @@ Triage the user's request and set up `.claude/state/workflow.json` so downstream
|
|
|
48
71
|
|
|
49
72
|
**For `spec`-entry track** (skip upstream): `Run /spec`, `Wait for /approve-spec <path>` (`needs_user`), `Run /tdd`, `Run /simplify`, `Run /security` (unless excepted), `Run /integrate`, `Run /document`, `Run /archive`, `Run /memory-flush`, `Wait for /grant-commit` (`needs_user`), `Run /changelog`, `Run /commit` — each with `addBlockedBy` set to the previous task's id.
|
|
50
73
|
|
|
51
|
-
**For `intake`-entry full track**: `Run /intake`, `Run /brd` (only if stakeholder-heavy), `Run /scout`, `Run /research`, `Run /spec`, `Wait for /approve-spec <path>` (`needs_user`), `Run /tdd` OR (`Run /swarm-plan`, `Wait for /approve-swarm <slug>` (`needs_user`), `Run /swarm-dispatch`), `Run /simplify`, `Run /security` (unless excepted), `Run /integrate`, `Run /document`, `Run /archive`, `Run /memory-flush`, `Wait for /grant-commit` (`needs_user`), `Run /changelog`, `Run /commit`. **On non-git projects the swarm branch SHALL NOT be seeded** — only `Run /tdd` goes in the list
|
|
74
|
+
**For `intake`-entry full track**: `Run /intake`, `Run /brd` (only if stakeholder-heavy), `Run /scout`, `Run /research`, `Run /spec`, `Wait for /approve-spec <path>` (`needs_user`), `Run /tdd` OR (`Run /swarm-plan`, `Wait for /approve-swarm <slug>` (`needs_user`), `Run /swarm-dispatch`), `Run /simplify`, `Run /security` (unless excepted), `Run /integrate`, `Run /document`, `Run /archive`, `Run /memory-flush`, `Wait for /grant-commit` (`needs_user`), `Run /changelog`, `Run /commit`. **On non-git projects the swarm branch SHALL NOT be seeded** — only `Run /tdd` goes in the list. Swarm-vs-solo is a Phase-6 main-context decision (per CLAUDE.md Article V) only on git projects; non-git workflows resolve to solo at triage time because `swarm-plan`, `approve-swarm`, and `swarm-dispatch` are already in `exceptions`. On non-git projects `changelog` is also auto-excepted alongside `commit`.
|
|
52
75
|
|
|
53
76
|
For every task: `subject` is imperative ("Run /scout for <slug>" / "Wait for /approve-spec <path>"); `description` names the phase + the slug; `metadata.phase` carries the phase name; consent-gate tasks set `metadata.needs_user: true`. Wire `addBlockedBy` so each task blocks until its predecessor completes — this surfaces the workflow's true dependency graph and prevents `/harness` from racing past a gate.
|
|
54
77
|
|
|
@@ -57,5 +80,5 @@ Triage the user's request and set up `.claude/state/workflow.json` so downstream
|
|
|
57
80
|
# Constraints
|
|
58
81
|
|
|
59
82
|
- NEVER skip triage by guessing from filename or diff alone. The user's natural-language framing is the primary signal.
|
|
60
|
-
- The Track Guard reads `
|
|
83
|
+
- The Track Guard reads `track_id` (post-§18) OR legacy `entry_phase`, plus `exceptions`. If the user wants to skip an optional phase (e.g. `security`), add it to `exceptions` — do not silently re-order `workflow.phases` in project.json.
|
|
61
84
|
- If a workflow.json already exists for an open request, ask whether to replace it (starts a fresh track) or add to `completed` (continuing the same track).
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Foundation helper for the triage skill (post-§18). Two modes:
|
|
3
|
+
//
|
|
4
|
+
// node .claude/skills/triage/seed-tasklist.mjs --validate-only
|
|
5
|
+
// Loads .claude/workflows.jsonl, validates against the §18 schema +
|
|
6
|
+
// invariants I1..I11, exits 0 on success or non-zero on failure (with
|
|
7
|
+
// a named error printed to stderr citing the offending track / node).
|
|
8
|
+
//
|
|
9
|
+
// node .claude/skills/triage/seed-tasklist.mjs <track_id> <slug> [--exclude-nodes id1,id2,...]
|
|
10
|
+
// Loads .claude/workflows.jsonl, validates, finds the chosen Track,
|
|
11
|
+
// materializes its DAG into a canonical TaskList shape, and prints the
|
|
12
|
+
// resulting JSON array to stdout. The optional --exclude-nodes flag
|
|
13
|
+
// skips the named nodes during emission (used to drop git-conditional
|
|
14
|
+
// consent gates on non-git projects).
|
|
15
|
+
//
|
|
16
|
+
// The triage skill body invokes this helper as a subprocess; for each entry
|
|
17
|
+
// in the printed TaskList it calls TaskCreate, then TaskUpdate to wire
|
|
18
|
+
// blockedBy by translating ordinal references to session-assigned task_ids.
|
|
19
|
+
|
|
20
|
+
import { resolve, dirname } from 'node:path';
|
|
21
|
+
import { fileURLToPath } from 'node:url';
|
|
22
|
+
import { validateWorkflowsJsonl } from '../../../src/cli/workflows-validator.js';
|
|
23
|
+
import { materializeTaskList } from '../../../src/cli/track-tasklist-materializer.js';
|
|
24
|
+
|
|
25
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
26
|
+
const REPO_ROOT = resolve(dirname(__filename), '../../..');
|
|
27
|
+
const WORKFLOWS_PATH = resolve(REPO_ROOT, '.claude/workflows.jsonl');
|
|
28
|
+
|
|
29
|
+
async function main(argv) {
|
|
30
|
+
const args = argv.slice(2);
|
|
31
|
+
const validateOnly = args.includes('--validate-only');
|
|
32
|
+
if (validateOnly) {
|
|
33
|
+
return runValidate();
|
|
34
|
+
}
|
|
35
|
+
const positional = args.filter((a) => !a.startsWith('--'));
|
|
36
|
+
const excludeFlag = args.find((a) => a.startsWith('--exclude-nodes='));
|
|
37
|
+
const excludedNodeIds = excludeFlag
|
|
38
|
+
? new Set(excludeFlag.slice('--exclude-nodes='.length).split(',').filter(Boolean))
|
|
39
|
+
: new Set();
|
|
40
|
+
if (positional.length < 2) {
|
|
41
|
+
printUsageAndExit(2);
|
|
42
|
+
}
|
|
43
|
+
return runMaterialize(positional[0], positional[1], excludedNodeIds);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function runValidate() {
|
|
47
|
+
const result = await validateWorkflowsJsonl(WORKFLOWS_PATH);
|
|
48
|
+
if (!result.ok) {
|
|
49
|
+
printValidationErrors(result.errors);
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
process.stderr.write(`validated ${result.tracks.length} tracks in ${WORKFLOWS_PATH}\n`);
|
|
53
|
+
process.exit(0);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function runMaterialize(trackId, slug, excludedNodeIds) {
|
|
57
|
+
const result = await validateWorkflowsJsonl(WORKFLOWS_PATH);
|
|
58
|
+
if (!result.ok) {
|
|
59
|
+
printValidationErrors(result.errors);
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
const track = result.tracks.find((t) => t.track_id === trackId);
|
|
63
|
+
if (!track) {
|
|
64
|
+
process.stderr.write(`track_id '${trackId}' not found in ${WORKFLOWS_PATH}\n`);
|
|
65
|
+
process.exit(1);
|
|
66
|
+
}
|
|
67
|
+
if (track.selectable !== true) {
|
|
68
|
+
process.stderr.write(`track '${trackId}' is selectable=false (sub-track); only selectable tracks may be materialized at workflow seed time.\n`);
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
const tasks = materializeTaskList(track, { slug });
|
|
72
|
+
const filtered = excludedNodeIds.size > 0
|
|
73
|
+
? tasks.filter((t) => !excludedTask(t, excludedNodeIds))
|
|
74
|
+
: tasks;
|
|
75
|
+
process.stdout.write(JSON.stringify(filtered, null, 2) + '\n');
|
|
76
|
+
process.exit(0);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function excludedTask(task, excludedNodeIds) {
|
|
80
|
+
if (excludedNodeIds.has(task.metadata?.phase)) return true;
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function printValidationErrors(errors) {
|
|
85
|
+
process.stderr.write(`validation failed (${errors.length} error(s)):\n`);
|
|
86
|
+
for (const err of errors) {
|
|
87
|
+
const parts = [err.kind];
|
|
88
|
+
if (err.line) parts.push(`line=${err.line}`);
|
|
89
|
+
if (err.track_id) parts.push(`track_id=${err.track_id}`);
|
|
90
|
+
if (err.node_id) parts.push(`node_id=${err.node_id}`);
|
|
91
|
+
process.stderr.write(` - ${parts.join(' ')}: ${err.message}\n`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function printUsageAndExit(code) {
|
|
96
|
+
process.stderr.write(
|
|
97
|
+
'usage:\n' +
|
|
98
|
+
' node .claude/skills/triage/seed-tasklist.mjs --validate-only\n' +
|
|
99
|
+
' node .claude/skills/triage/seed-tasklist.mjs <track_id> <slug> [--exclude-nodes id1,id2,...]\n'
|
|
100
|
+
);
|
|
101
|
+
process.exit(code);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
main(process.argv).catch((err) => {
|
|
105
|
+
process.stderr.write(`fatal: ${err.message}\n`);
|
|
106
|
+
process.exit(2);
|
|
107
|
+
});
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: upgrade-project
|
|
3
|
+
owner: baseline
|
|
4
|
+
description: Reconcile baseline-versioned files that the `create-baseline upgrade` CLI staged for LLM-assisted semantic merge. Use when the CLI prints "Open Claude Code and run /upgrade-project to reconcile". Reads the stage manifest at `.claude/state/upgrade/<ts>/manifest.json`, reasons through each pending file's three-way delta in main context, writes a reconciled LOCAL, then deletes the stage when every file lands. Supports `--dry-run` (preview the reconciled diff without writing) and a structured "needs-user-input" fallback when the conflict cannot be disambiguated automatically.
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# /upgrade-project — semantic-merge reconciliation for baseline files
|
|
8
|
+
|
|
9
|
+
You are reconciling files that `create-baseline upgrade` decided required **semantic merge** rather than mechanical merge. The CLI has already detected per-file customization, classified each file as tier 3 (SEMANTIC) at build time, and staged the three states (BASE / INCOMING / LOCAL) for you to reason about in main context. This skill is the only sanctioned way to drive that staged state to RECONCILED.
|
|
10
|
+
|
|
11
|
+
This skill is **maintenance work**, not a workflow phase. It is invoked reactively whenever the upgrade CLI prints the "run /upgrade-project to reconcile" pointer. It does not appear in `.claude/state/workflow.json`, does not require `/triage`, and does not trigger consent gates.
|
|
12
|
+
|
|
13
|
+
## When to use
|
|
14
|
+
|
|
15
|
+
- The user just ran `npx @friedbotstudio/create-baseline upgrade <target>` and the CLI exited 5 with a "Pending semantic-merge stage at <ts>" message.
|
|
16
|
+
- The user types `/upgrade-project` or asks "reconcile the staged files".
|
|
17
|
+
- A previous `/upgrade-project` invocation hit a `NEEDS_USER_INPUT` fallback, the user provided direction, and you re-invoke to pick up where you left off.
|
|
18
|
+
|
|
19
|
+
## Inputs (read from disk)
|
|
20
|
+
|
|
21
|
+
For each stage directory under `.claude/state/upgrade/`:
|
|
22
|
+
|
|
23
|
+
- `manifest.json` — the **stage manifest** the CLI wrote. Schema:
|
|
24
|
+
```json
|
|
25
|
+
{
|
|
26
|
+
"stage_version": 1,
|
|
27
|
+
"slug": "upgrade-flow-rework",
|
|
28
|
+
"created_at": "2026-05-20T14:49:00.000Z",
|
|
29
|
+
"baseline_version_from": "0.4.0",
|
|
30
|
+
"baseline_version_to": "0.5.0",
|
|
31
|
+
"files": [
|
|
32
|
+
{
|
|
33
|
+
"rel": "docs/init/seed.md",
|
|
34
|
+
"base_sha256": "<hex>",
|
|
35
|
+
"incoming_sha256": "<hex>",
|
|
36
|
+
"local_sha256": "<hex>",
|
|
37
|
+
"status": "PENDING"
|
|
38
|
+
}
|
|
39
|
+
]
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
- For each entry in `files`, three artifacts are present:
|
|
43
|
+
- `<rel>.baseline-base` — the **BASE** content (the file as it was when the user last installed the baseline).
|
|
44
|
+
- `<rel>.baseline-incoming` — the **INCOMING** content (the file as it ships in the new baseline; INCOMING and REMOTE are the same thing).
|
|
45
|
+
- The LOCAL file remains at its real path inside the target tree (untouched by the CLI).
|
|
46
|
+
|
|
47
|
+
## Procedure
|
|
48
|
+
|
|
49
|
+
1. **Discover the stage.** Read `.claude/state/upgrade/` and pick the most-recent stage directory whose manifest has at least one file with `status: PENDING` or `status: NEEDS_USER_INPUT`. If no such stage exists, tell the user "No pending stage to reconcile" and exit.
|
|
50
|
+
2. **Per file**, in the order they appear in the stage manifest:
|
|
51
|
+
- Read BASE, INCOMING, and LOCAL.
|
|
52
|
+
- Reason about the three-way delta. Identify what changed between BASE → INCOMING (the upstream edit), what changed between BASE → LOCAL (the user edit), and where they conflict.
|
|
53
|
+
- If both edits are textually non-overlapping, the CLI would have routed the file to tier 2 (mechanical merge). The fact that the file is in tier 3 means structural reconciliation is needed — most commonly: both sides inserted content at the same structural anchor (a new section, a new numbered article, a new TOC entry).
|
|
54
|
+
- Apply the **zero-drift renumbering rule** below.
|
|
55
|
+
- Write the reconciled bytes to the LOCAL path.
|
|
56
|
+
- Update the stage manifest entry's `status` to `RECONCILED`.
|
|
57
|
+
3. **Finalize the stage.** When every entry's status is `RECONCILED`, delete the stage directory (`rm -rf .claude/state/upgrade/<ts>/`). Report per-file status to the user.
|
|
58
|
+
|
|
59
|
+
## The zero-drift renumbering rule (binding)
|
|
60
|
+
|
|
61
|
+
When BASE → INCOMING adds a new structural entry (a new Article, a new section, a new numbered item) at position N, and BASE → LOCAL added the user's own entry at the same position N, you SHALL renumber the user's entry to the **next available** slot (N+1) — you SHALL **never fold** the user's entry into an existing baseline section.
|
|
62
|
+
|
|
63
|
+
Concrete example (the Article-XI reproducer):
|
|
64
|
+
- BASE `seed.md` ends at Article X.
|
|
65
|
+
- LOCAL has added a project-specific `## Article XI (user content)`.
|
|
66
|
+
- INCOMING ships a new baseline `## Article XI (Skill provenance and the baseline manifest)`.
|
|
67
|
+
|
|
68
|
+
The reconciled `seed.md` SHALL contain:
|
|
69
|
+
- `## Article XI` — the baseline's content (verbatim).
|
|
70
|
+
- `## Article XII` — the user's prior content, renumbered.
|
|
71
|
+
- Every cross-reference in the document that pointed to "Article XI" SHALL be updated to point to either Article XI (when the reference was always to the new baseline content) or Article XII (when the reference was to what was previously Article XI). Surface ambiguous references as `NEEDS_USER_INPUT` per the fallback below.
|
|
72
|
+
|
|
73
|
+
The reason **shift, never fold**: the next baseline upgrade SHALL produce zero new staging entries for this file. If the user's content were folded into an existing baseline section, the next upgrade would re-detect a customization and re-stage. The renumbering preserves both bodies as independent structural units, so subsequent upgrades see exactly the baseline-owned portion (Articles I–XI) as unchanged.
|
|
74
|
+
|
|
75
|
+
The same principle applies recursively. If a later baseline ships Article XII and the user's content has been at Article XII since the prior upgrade, shift the user's content to Article XIII. Always shift to the next available slot.
|
|
76
|
+
|
|
77
|
+
## `--dry-run` mode
|
|
78
|
+
|
|
79
|
+
When invoked with `args=dry-run` (e.g., `/upgrade-project dry-run`):
|
|
80
|
+
|
|
81
|
+
- Per file, produce the reconciled bytes in your reasoning, then emit a colorized unified diff (LOCAL vs reconciled) to the skill's terminal output rather than writing.
|
|
82
|
+
- DO NOT modify any LOCAL file.
|
|
83
|
+
- DO NOT update the stage manifest (statuses stay PENDING / NEEDS_USER_INPUT).
|
|
84
|
+
- DO NOT delete the stage directory.
|
|
85
|
+
- Tell the user: "Dry-run complete. Re-run without `dry-run` to apply."
|
|
86
|
+
|
|
87
|
+
Dry-run mode is for building trust in early use. After the first few successful reconciliations, the user typically stops dry-running.
|
|
88
|
+
|
|
89
|
+
## Fallback — NEEDS_USER_INPUT
|
|
90
|
+
|
|
91
|
+
When you genuinely cannot disambiguate intent — the conflict has multiple plausible reconciliations and you cannot pick one without guessing the user's preference — apply the **NEEDS_USER_INPUT** fallback rather than picking arbitrarily:
|
|
92
|
+
|
|
93
|
+
1. Update the stage manifest entry's `status` to `NEEDS_USER_INPUT`.
|
|
94
|
+
2. Leave BASE, INCOMING, and LOCAL artifacts in place (do NOT delete the stage).
|
|
95
|
+
3. Surface a targeted question to the user that names the file, summarizes the conflict in one sentence, and offers concrete options. Example: "Cannot disambiguate `docs/init/seed.md` Article XI: the baseline's new Article XI heading shares the user's chosen heading text. Should I (a) treat them as the same article and merge bodies, or (b) renumber the user's article to XII?"
|
|
96
|
+
4. Exit clean. The user provides direction in their next prompt. A subsequent `/upgrade-project` invocation re-reads the stage manifest, finds the `NEEDS_USER_INPUT` entry, and re-attempts with the user's direction.
|
|
97
|
+
|
|
98
|
+
Use this fallback sparingly. The rework's whole point is that LLM judgment exceeds `git merge-file` for structural conflicts; if you punt to NEEDS_USER_INPUT for trivial reconciliations, you defeat the purpose.
|
|
99
|
+
|
|
100
|
+
## Constraints
|
|
101
|
+
|
|
102
|
+
- **Validate `rel` before writing.** Before writing reconciled bytes to LOCAL, you SHALL verify that the resolved absolute path of `<target>/<rel>` is a descendant of `target`. A `rel` value that escapes the target tree (`../`, absolute path, symlink-resolved escape) SHALL be rejected as a `NEEDS_USER_INPUT` fallback with the reason `path-traversal-rejected`. The CLI's stage writer never produces escaping `rel` values, so this catches only tampered stage manifests from a local attacker with `.claude/state/` write access — defense in depth.
|
|
103
|
+
- **No write outside the stage directory and the LOCAL path.** You SHALL NOT touch `.claude/.baseline-prior/`, the installed `.baseline-manifest.json`, or any other CLI state.
|
|
104
|
+
- **No partial writes per file.** The reconciled LOCAL must be the complete final content. If you cannot produce a complete reconciliation, use the NEEDS_USER_INPUT fallback and leave LOCAL unmodified.
|
|
105
|
+
- **Honor Article XI of CLAUDE.md.** This skill only touches files explicitly staged by the CLI — which, by construction, are baseline-owned. User-added files at colliding paths are never staged.
|
|
106
|
+
- **No commits.** Reconciled files land on the working tree; the user inspects via `git diff` and commits when satisfied.
|
|
107
|
+
- **No re-fetching from npm.** BASE is already on disk in the stage; no network round-trip needed.
|
|
108
|
+
|
|
109
|
+
## Output
|
|
110
|
+
|
|
111
|
+
After running, report per file:
|
|
112
|
+
|
|
113
|
+
```
|
|
114
|
+
# /upgrade-project — <stage_ts>
|
|
115
|
+
|
|
116
|
+
- <rel>: RECONCILED (N lines changed)
|
|
117
|
+
- <rel>: NEEDS_USER_INPUT — <one-sentence question>
|
|
118
|
+
- <rel>: SKIPPED (dry-run)
|
|
119
|
+
|
|
120
|
+
Stage deleted: yes | no (NEEDS_USER_INPUT pending)
|
|
121
|
+
```
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
{"$schema":"./schemas/workflow-track.v1.json","track_id":"intake-full","name":"Intake-entry full pipeline","description":"Canonical 11-phase pipeline for new features that need full design upfront (intake → scout → research → spec → approval → tdd/swarm → simplify → security → integrate → document → archive → memory-flush → grant-commit → changelog → commit).","selectable":true,"selector_hints":["build a new feature with full design upfront","add new functionality requiring a written spec","constitutional change or architectural refactor"],"preconditions":[],"invariants":["commits","requires_spec"],"nodes":[{"id":"intake","type":"task","skill":"intake","depends_on":[],"blocks":["scout"],"can_parallel":false,"needs_user":false,"activeForm":"Running intake","metadata":{"phase":"intake"}},{"id":"scout","type":"task","skill":"scout","depends_on":["intake"],"blocks":["research"],"can_parallel":false,"needs_user":false,"activeForm":"Running scout","metadata":{"phase":"scout"}},{"id":"research","type":"task","skill":"research","depends_on":["scout"],"blocks":["spec"],"can_parallel":false,"needs_user":false,"activeForm":"Running research","metadata":{"phase":"research"}},{"id":"spec","type":"task","skill":"spec","depends_on":["research"],"blocks":["approve-spec"],"can_parallel":false,"needs_user":false,"activeForm":"Running spec","metadata":{"phase":"spec"}},{"id":"approve-spec","type":"task","skill":"approve-spec","depends_on":["spec"],"blocks":["implementation"],"can_parallel":false,"needs_user":true,"activeForm":"Awaiting spec approval","metadata":{"phase":"approve-spec","needs_user":true}},{"id":"implementation","type":"selector","alternates":[{"sub_track":"swarm-implementation","preconditions":[{"name":"requires_git"},{"name":"requires_min_components","argument":"3"}],"description":"Swarm path for git projects with 3+ independent components"},{"sub_track":"tdd-worker-chain","preconditions":[],"description":"Solo TDD fallback (default)"}],"depends_on":["approve-spec"],"blocks":["simplify"],"can_parallel":false,"needs_user":false},{"id":"simplify","type":"task","skill":"simplify","depends_on":["implementation"],"blocks":["security"],"can_parallel":false,"needs_user":false,"activeForm":"Running simplify","metadata":{"phase":"simplify"}},{"id":"security","type":"task","skill":"security","depends_on":["simplify"],"blocks":["integrate"],"can_parallel":false,"needs_user":false,"activeForm":"Running security","metadata":{"phase":"security"}},{"id":"integrate","type":"task","skill":"integrate","depends_on":["security"],"blocks":["document"],"can_parallel":false,"needs_user":false,"activeForm":"Running integrate","metadata":{"phase":"integrate"}},{"id":"document","type":"task","skill":"document","depends_on":["integrate"],"blocks":["archive"],"can_parallel":false,"needs_user":false,"activeForm":"Running document","metadata":{"phase":"document"}},{"id":"archive","type":"task","skill":"archive","depends_on":["document"],"blocks":["memory-flush"],"can_parallel":false,"needs_user":false,"activeForm":"Running archive","metadata":{"phase":"archive"}},{"id":"memory-flush","type":"task","skill":"memory-flush","depends_on":["archive"],"blocks":["grant-commit"],"can_parallel":false,"needs_user":false,"activeForm":"Running memory-flush","metadata":{"phase":"memory-flush"}},{"id":"grant-commit","type":"task","skill":"grant-commit","depends_on":["memory-flush"],"blocks":["changelog"],"can_parallel":false,"needs_user":true,"activeForm":"Awaiting commit consent","metadata":{"phase":"grant-commit","needs_user":true}},{"id":"changelog","type":"task","skill":"changelog","depends_on":["grant-commit"],"blocks":["commit"],"can_parallel":false,"needs_user":false,"activeForm":"Running changelog","metadata":{"phase":"changelog"}},{"id":"commit","type":"task","skill":"commit","depends_on":["changelog"],"blocks":[],"can_parallel":false,"needs_user":false,"activeForm":"Running commit","metadata":{"phase":"commit"}}]}
|
|
2
|
+
{"$schema":"./schemas/workflow-track.v1.json","track_id":"spec-entry","name":"Spec-entry bugfix pipeline","description":"Bugfix workflow that starts at /spec; skips intake/scout/research. For known-behavior bugs needing a contract-level fix.","selectable":true,"selector_hints":["bug needs a spec","contract-level bugfix","behaviour change requires written design"],"preconditions":[],"invariants":["commits","requires_spec"],"nodes":[{"id":"spec","type":"task","skill":"spec","depends_on":[],"blocks":["approve-spec"],"can_parallel":false,"needs_user":false,"activeForm":"Running spec","metadata":{"phase":"spec"}},{"id":"approve-spec","type":"task","skill":"approve-spec","depends_on":["spec"],"blocks":["tdd"],"can_parallel":false,"needs_user":true,"activeForm":"Awaiting spec approval","metadata":{"phase":"approve-spec","needs_user":true}},{"id":"tdd","type":"task","skill":"tdd","depends_on":["approve-spec"],"blocks":["simplify"],"can_parallel":false,"needs_user":false,"activeForm":"Running TDD","metadata":{"phase":"tdd"}},{"id":"simplify","type":"task","skill":"simplify","depends_on":["tdd"],"blocks":["security"],"can_parallel":false,"needs_user":false,"activeForm":"Running simplify","metadata":{"phase":"simplify"}},{"id":"security","type":"task","skill":"security","depends_on":["simplify"],"blocks":["integrate"],"can_parallel":false,"needs_user":false,"activeForm":"Running security","metadata":{"phase":"security"}},{"id":"integrate","type":"task","skill":"integrate","depends_on":["security"],"blocks":["document"],"can_parallel":false,"needs_user":false,"activeForm":"Running integrate","metadata":{"phase":"integrate"}},{"id":"document","type":"task","skill":"document","depends_on":["integrate"],"blocks":["archive"],"can_parallel":false,"needs_user":false,"activeForm":"Running document","metadata":{"phase":"document"}},{"id":"archive","type":"task","skill":"archive","depends_on":["document"],"blocks":["memory-flush"],"can_parallel":false,"needs_user":false,"activeForm":"Running archive","metadata":{"phase":"archive"}},{"id":"memory-flush","type":"task","skill":"memory-flush","depends_on":["archive"],"blocks":["grant-commit"],"can_parallel":false,"needs_user":false,"activeForm":"Running memory-flush","metadata":{"phase":"memory-flush"}},{"id":"grant-commit","type":"task","skill":"grant-commit","depends_on":["memory-flush"],"blocks":["changelog"],"can_parallel":false,"needs_user":true,"activeForm":"Awaiting commit consent","metadata":{"phase":"grant-commit","needs_user":true}},{"id":"changelog","type":"task","skill":"changelog","depends_on":["grant-commit"],"blocks":["commit"],"can_parallel":false,"needs_user":false,"activeForm":"Running changelog","metadata":{"phase":"changelog"}},{"id":"commit","type":"task","skill":"commit","depends_on":["changelog"],"blocks":[],"can_parallel":false,"needs_user":false,"activeForm":"Running commit","metadata":{"phase":"commit"}}]}
|
|
3
|
+
{"$schema":"./schemas/workflow-track.v1.json","track_id":"tdd-quickfix","name":"TDD-entry quickfix pipeline","description":"Quickfix workflow that starts directly at /tdd. For localized misbehaviour with a known failing case; no spec needed.","selectable":true,"selector_hints":["quickfix with known failing test","localized bug; no spec needed","small bundled patch with a clear failing case"],"preconditions":[],"invariants":["commits"],"nodes":[{"id":"tdd","type":"task","skill":"tdd","depends_on":[],"blocks":["simplify"],"can_parallel":false,"needs_user":false,"activeForm":"Running TDD","metadata":{"phase":"tdd"}},{"id":"simplify","type":"task","skill":"simplify","depends_on":["tdd"],"blocks":["security"],"can_parallel":false,"needs_user":false,"activeForm":"Running simplify","metadata":{"phase":"simplify"}},{"id":"security","type":"task","skill":"security","depends_on":["simplify"],"blocks":["integrate"],"can_parallel":false,"needs_user":false,"activeForm":"Running security","metadata":{"phase":"security"}},{"id":"integrate","type":"task","skill":"integrate","depends_on":["security"],"blocks":["document"],"can_parallel":false,"needs_user":false,"activeForm":"Running integrate","metadata":{"phase":"integrate"}},{"id":"document","type":"task","skill":"document","depends_on":["integrate"],"blocks":["archive"],"can_parallel":false,"needs_user":false,"activeForm":"Running document","metadata":{"phase":"document"}},{"id":"archive","type":"task","skill":"archive","depends_on":["document"],"blocks":["memory-flush"],"can_parallel":false,"needs_user":false,"activeForm":"Running archive","metadata":{"phase":"archive"}},{"id":"memory-flush","type":"task","skill":"memory-flush","depends_on":["archive"],"blocks":["grant-commit"],"can_parallel":false,"needs_user":false,"activeForm":"Running memory-flush","metadata":{"phase":"memory-flush"}},{"id":"grant-commit","type":"task","skill":"grant-commit","depends_on":["memory-flush"],"blocks":["changelog"],"can_parallel":false,"needs_user":true,"activeForm":"Awaiting commit consent","metadata":{"phase":"grant-commit","needs_user":true}},{"id":"changelog","type":"task","skill":"changelog","depends_on":["grant-commit"],"blocks":["commit"],"can_parallel":false,"needs_user":false,"activeForm":"Running changelog","metadata":{"phase":"changelog"}},{"id":"commit","type":"task","skill":"commit","depends_on":["changelog"],"blocks":[],"can_parallel":false,"needs_user":false,"activeForm":"Running commit","metadata":{"phase":"commit"}}]}
|
|
4
|
+
{"$schema":"./schemas/workflow-track.v1.json","track_id":"chore","name":"Chore track","description":"Stripped-down pipeline for changes that need no failing-test-driven code change: documentation edits, configuration tweaks, formatting, dependency bumps with no project code change.","selectable":true,"selector_hints":["documentation edit","governance count refresh","configuration tweak","formatting or typo fix","skill consolidation move"],"preconditions":[],"invariants":["commits","chore"],"nodes":[{"id":"chore","type":"task","skill":"chore","depends_on":[],"blocks":["memory-flush"],"can_parallel":false,"needs_user":false,"activeForm":"Running chore","metadata":{"phase":"chore"}},{"id":"memory-flush","type":"task","skill":"memory-flush","depends_on":["chore"],"blocks":["grant-commit"],"can_parallel":false,"needs_user":false,"activeForm":"Running memory-flush","metadata":{"phase":"memory-flush"}},{"id":"grant-commit","type":"task","skill":"grant-commit","depends_on":["memory-flush"],"blocks":["changelog"],"can_parallel":false,"needs_user":true,"activeForm":"Awaiting commit consent","metadata":{"phase":"grant-commit","needs_user":true}},{"id":"changelog","type":"task","skill":"changelog","depends_on":["grant-commit"],"blocks":["commit"],"can_parallel":false,"needs_user":false,"activeForm":"Running changelog","metadata":{"phase":"changelog"}},{"id":"commit","type":"task","skill":"commit","depends_on":["changelog"],"blocks":[],"can_parallel":false,"needs_user":false,"activeForm":"Running commit","metadata":{"phase":"commit"}}]}
|
|
5
|
+
{"$schema":"./schemas/workflow-track.v1.json","track_id":"swarm-implementation","name":"Swarm implementation sub-track","description":"Parallel implementation via swarm-plan + approve-swarm + swarm-dispatch. Selected by intake-full's selector node when requires_git and requires_min_components:3 preconditions pass.","selectable":false,"selector_hints":[],"preconditions":[{"name":"requires_git"}],"invariants":["requires_swarm","git_only"],"nodes":[{"id":"swarm-plan","type":"task","skill":"swarm-plan","depends_on":[],"blocks":["approve-swarm"],"can_parallel":false,"needs_user":false,"activeForm":"Running swarm-plan","metadata":{"phase":"swarm-plan"}},{"id":"approve-swarm","type":"task","skill":"approve-swarm","depends_on":["swarm-plan"],"blocks":["swarm-dispatch"],"can_parallel":false,"needs_user":true,"activeForm":"Awaiting swarm approval","metadata":{"phase":"approve-swarm","needs_user":true}},{"id":"swarm-dispatch","type":"task","skill":"swarm-dispatch","depends_on":["approve-swarm"],"blocks":[],"can_parallel":false,"needs_user":false,"activeForm":"Running swarm-dispatch","metadata":{"phase":"swarm-dispatch"}}]}
|
|
6
|
+
{"$schema":"./schemas/workflow-track.v1.json","track_id":"tdd-worker-chain","name":"TDD worker-chain sub-track","description":"Solo TDD path. Fallback alternate for intake-full when swarm preconditions fail or the user picks solo explicitly.","selectable":false,"selector_hints":[],"preconditions":[],"invariants":[],"nodes":[{"id":"tdd","type":"task","skill":"tdd","depends_on":[],"blocks":[],"can_parallel":false,"needs_user":false,"activeForm":"Running TDD","metadata":{"phase":"tdd"}}]}
|