@dzhechkov/p-replicator 1.3.0 → 1.5.3
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 +1097 -160
- package/bin/cli.js +0 -0
- package/package.json +7 -2
- package/src/cli.js +23 -6
- package/src/commands/doctor.js +46 -29
- package/src/commands/init.js +61 -8
- package/src/commands/list.js +5 -26
- package/src/commands/update.js +73 -7
- package/src/commands/verify.js +111 -0
- package/src/utils.js +275 -4
- package/templates/.claude/commands/deploy.md +100 -0
- package/templates/.claude/commands/docs.md +79 -0
- package/templates/.claude/commands/feature.md +134 -0
- package/templates/.claude/commands/go.md +115 -0
- package/templates/.claude/commands/myinsights.md +72 -0
- package/templates/.claude/commands/next.md +110 -0
- package/templates/.claude/commands/plan.md +88 -0
- package/templates/.claude/commands/replicate.md +103 -17
- package/templates/.claude/commands/run.md +151 -0
- package/templates/.claude/commands/start.md +88 -0
- package/templates/.claude/hooks/autocommit-insights.cjs +39 -0
- package/templates/.claude/hooks/autocommit-plans.cjs +39 -0
- package/templates/.claude/hooks/autocommit-roadmap.cjs +44 -0
- package/templates/.claude/hooks/session-insights.cjs +28 -0
- package/templates/.claude/hooks/state-update.cjs +79 -0
- package/templates/.claude/hooks/statusline.cjs +399 -0
- package/templates/.claude/rules/feature-lifecycle.md +145 -0
- package/templates/.claude/rules/git-workflow.md +74 -0
- package/templates/.claude/rules/insights-capture.md +77 -0
- package/templates/.claude/rules/replicate-pipeline.md +92 -19
- package/templates/.claude/settings.json +44 -0
- package/templates/.claude/skills/brutal-honesty-review/scripts/assess-code.sh +0 -0
- package/templates/.claude/skills/brutal-honesty-review/scripts/assess-tests.sh +0 -0
- package/templates/.claude/skills/goap-research-ed25519/scripts/ed25519_verifier.py +0 -0
- package/templates/.claude/skills/goap-research-ed25519/scripts/goap_planner.py +0 -0
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Autonomous build loop — picks next feature from roadmap, dispatches to /go, repeats until exhausted. Modes: `mvp` (default — only features tagged mvp), `all` (everything in `next` or `planned`). Auto-commits after each feature. Supports `--feature-branches` for per-feature git branches (teaching/demo workflow).
|
|
3
|
+
argument-hint: '[mvp | all] [--feature-branches] [--auto-merge]'
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# /run $ARGUMENTS
|
|
7
|
+
|
|
8
|
+
## Purpose
|
|
9
|
+
|
|
10
|
+
One-command autonomous feature build. Reads `.claude/feature-roadmap.json`,
|
|
11
|
+
loops through features via `/next` → `/go`, marks them done, commits, and
|
|
12
|
+
continues until the scoped list is exhausted.
|
|
13
|
+
|
|
14
|
+
> **PROCESS COMPLIANCE — BLOCKING RULES:**
|
|
15
|
+
> - MUST delegate to `/next`, `/go`, `/plan`, `/feature` — NEVER spawn raw Agent tools directly
|
|
16
|
+
> - MUST commit after each feature (one feature = one commit cluster)
|
|
17
|
+
> - FORBIDDEN: bypassing /go's complexity routing
|
|
18
|
+
> - FORBIDDEN: batching multiple features in a single commit wave
|
|
19
|
+
|
|
20
|
+
## Step 0: Parse Scope
|
|
21
|
+
|
|
22
|
+
| `$ARGUMENTS` | Filter applied to roadmap |
|
|
23
|
+
|--------------|---------------------------|
|
|
24
|
+
| `mvp` (default if empty) | Only features with `priority: "mvp"` and status in `next`/`planned` |
|
|
25
|
+
| `all` | All features with status in `next`/`planned` |
|
|
26
|
+
|
|
27
|
+
If `.claude/feature-roadmap.json` does not exist → halt with:
|
|
28
|
+
```
|
|
29
|
+
Roadmap not found. Run /replicate first or create .claude/feature-roadmap.json manually.
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Step 1: Bootstrap Check
|
|
33
|
+
|
|
34
|
+
If `CLAUDE.md` exists but core scaffolding is missing (e.g., no `package.json`,
|
|
35
|
+
no `docker-compose.yml`), call `/start` once before the loop. Skip if
|
|
36
|
+
scaffolding already in place.
|
|
37
|
+
|
|
38
|
+
## Step 2: Feature Loop
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
while features_remaining_in_scope:
|
|
42
|
+
feature_id = /next # pick highest-priority next/planned
|
|
43
|
+
if not feature_id: break
|
|
44
|
+
/go feature_id # complexity router → /plan or /feature
|
|
45
|
+
verify completion (tests green, code committed)
|
|
46
|
+
mark roadmap entry: status = "done"
|
|
47
|
+
git commit -m "docs(roadmap): mark <id> as done"
|
|
48
|
+
git push origin HEAD
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Between iterations, briefly summarize progress: `Completed N of M features in scope`.
|
|
52
|
+
|
|
53
|
+
## Step 3: Final Summary
|
|
54
|
+
|
|
55
|
+
When the loop terminates (scope exhausted or user interrupt):
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
═══════════════════════════════════════════════════════════════
|
|
59
|
+
✅ /run complete — scope: <mvp|all>
|
|
60
|
+
Completed: <N> features
|
|
61
|
+
Total commits: <count>
|
|
62
|
+
Time elapsed: <duration>
|
|
63
|
+
Next: /docs to generate documentation, /harvest to extract knowledge
|
|
64
|
+
═══════════════════════════════════════════════════════════════
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Error Recovery
|
|
68
|
+
|
|
69
|
+
If a single feature fails (test red, validation blocked), `/run`:
|
|
70
|
+
1. Marks that feature as `blocked` with reason
|
|
71
|
+
2. Continues to next feature in scope
|
|
72
|
+
3. At the end, lists all blocked features for manual review
|
|
73
|
+
|
|
74
|
+
`/run` does NOT abort the whole loop on one feature failure — autonomous mode
|
|
75
|
+
is designed for unattended operation.
|
|
76
|
+
|
|
77
|
+
## Flag: `--feature-branches` (v1.5.0)
|
|
78
|
+
|
|
79
|
+
Create a separate git branch per feature. Useful for **teaching/demo
|
|
80
|
+
workflows** where each feature must be a discrete unit the instructor can
|
|
81
|
+
checkout independently.
|
|
82
|
+
|
|
83
|
+
### Branch naming format
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
feature/{NNN}-{id}
|
|
87
|
+
```
|
|
88
|
+
where `NNN` is a zero-padded 3-digit feature number from the roadmap, and
|
|
89
|
+
`{id}` is the feature's slug. Example: `feature/001-auth-jwt`.
|
|
90
|
+
|
|
91
|
+
### Per-feature workflow with --feature-branches
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
For each feature in scope:
|
|
95
|
+
1. Verify on `main` branch (else fail with hint to switch)
|
|
96
|
+
2. If working tree is dirty:
|
|
97
|
+
git stash push -u -m "auto-stash before /run feature-branches"
|
|
98
|
+
3. Read roadmap entry; if feature has no `number`, assign max(numbers) + 1
|
|
99
|
+
and persist back to .claude/feature-roadmap.json
|
|
100
|
+
4. git checkout -b feature/{NNN}-{id} # branched from main
|
|
101
|
+
5. /go {feature-id} # delegate to standard pipeline
|
|
102
|
+
6. git push origin feature/{NNN}-{id} --set-upstream
|
|
103
|
+
7. Mark roadmap entry: status=done, branch="feature/{NNN}-{id}"
|
|
104
|
+
8. git checkout main # ready for next iteration
|
|
105
|
+
9. If --auto-merge ALSO passed:
|
|
106
|
+
git merge --no-ff feature/{NNN}-{id} -m "merge: feature/{NNN}-{id}"
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Companion flag: `--auto-merge`
|
|
110
|
+
|
|
111
|
+
Off by default. When passed alongside `--feature-branches`, each feature
|
|
112
|
+
branch is merged into `main` (with `--no-ff`) immediately after completion.
|
|
113
|
+
Without `--auto-merge`, branches are pushed but **NOT** merged — instructor
|
|
114
|
+
or reviewer is expected to merge via PR or manually.
|
|
115
|
+
|
|
116
|
+
### Roadmap schema extension (post v1.5.0)
|
|
117
|
+
|
|
118
|
+
```json
|
|
119
|
+
{
|
|
120
|
+
"id": "auth-jwt",
|
|
121
|
+
"number": 1,
|
|
122
|
+
"branch": "feature/001-auth-jwt",
|
|
123
|
+
"name": "JWT-based authentication",
|
|
124
|
+
"priority": "mvp",
|
|
125
|
+
"status": "done",
|
|
126
|
+
...
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
`number` is auto-assigned on first encounter when `--feature-branches` is
|
|
131
|
+
used. `branch` is populated when the feature completes successfully.
|
|
132
|
+
|
|
133
|
+
### Use cases
|
|
134
|
+
|
|
135
|
+
- **Teaching:** instructor checks out `feature/003-payment` to demo a specific
|
|
136
|
+
feature. All branches are pushed, easy to fetch and explore.
|
|
137
|
+
- **Code review:** each feature has standalone branch for PR-based review.
|
|
138
|
+
- **Experimentation:** abandon a branch cleanly without disturbing main.
|
|
139
|
+
|
|
140
|
+
### Anti-patterns
|
|
141
|
+
|
|
142
|
+
- Running `--feature-branches` while on a feature branch (not main) → fails
|
|
143
|
+
- Mixing `--feature-branches` with non-branch features in the same session
|
|
144
|
+
→ mark already-merged features explicitly with `branch: "main"` to avoid retry
|
|
145
|
+
|
|
146
|
+
## Related
|
|
147
|
+
|
|
148
|
+
- `/next` — picks next feature (called per iteration)
|
|
149
|
+
- `/go` — complexity router (called per feature; also accepts `--feature-branches`)
|
|
150
|
+
- `/plan`, `/feature` — actual implementation pipelines
|
|
151
|
+
- `.claude/feature-roadmap.json` — source of truth for what to build
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Bootstrap project from SPARC documentation. Generates monorepo skeleton, packages, Docker configs, database schema, core modules, and basic tests in 4 phases (Foundation → Packages parallel → Integration → Finalize). Reads `docs/` as source of truth.
|
|
3
|
+
argument-hint: '[--skip-tests | --skip-seed | --dry-run]'
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# /start $ARGUMENTS
|
|
7
|
+
|
|
8
|
+
## Purpose
|
|
9
|
+
|
|
10
|
+
One-command project generation from SPARC documentation → working monorepo
|
|
11
|
+
with `docker compose up`. Reads `docs/` (NOT memory), maximizes parallelism
|
|
12
|
+
via `Task` tool, commits per logical change for safe error recovery.
|
|
13
|
+
|
|
14
|
+
## Prerequisites
|
|
15
|
+
|
|
16
|
+
- SPARC documents in `docs/` (output of `/replicate` Phase 1)
|
|
17
|
+
- `CLAUDE.md` at project root
|
|
18
|
+
- Docker + Docker Compose installed
|
|
19
|
+
- Git initialized
|
|
20
|
+
|
|
21
|
+
## Phases
|
|
22
|
+
|
|
23
|
+
### Phase 1: Foundation (sequential)
|
|
24
|
+
|
|
25
|
+
1. **Read all SPARC docs** to build full context:
|
|
26
|
+
- `docs/Architecture.md` → monorepo structure, Docker Compose, tech stack
|
|
27
|
+
- `docs/Specification.md` → data model, API endpoints, NFRs
|
|
28
|
+
- `docs/Pseudocode.md` → core algorithms
|
|
29
|
+
- `docs/Completion.md` → env config, deployment
|
|
30
|
+
- `docs/PRD.md` → features (for README)
|
|
31
|
+
- `docs/Refinement.md` → edge cases, testing strategy
|
|
32
|
+
|
|
33
|
+
2. **Generate root configs:** `package.json` (monorepo workspaces),
|
|
34
|
+
`docker-compose.yml`, `.env.example`, `.gitignore`, `tsconfig.base.json`.
|
|
35
|
+
|
|
36
|
+
3. **Git commit:** `chore: project root configuration`
|
|
37
|
+
|
|
38
|
+
### Phase 2: Packages (PARALLEL via Task tool ⚡)
|
|
39
|
+
|
|
40
|
+
For EACH package in Architecture.md, spawn an independent Task referencing
|
|
41
|
+
SOURCE DOCS (not memory):
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
### Task <X>: packages/<name> ⚡
|
|
45
|
+
Read and use as source:
|
|
46
|
+
- docs/Specification.md → data model → ORM schema
|
|
47
|
+
- docs/Architecture.md → API endpoints → routes
|
|
48
|
+
- docs/Pseudocode.md → algorithms → service layer
|
|
49
|
+
|
|
50
|
+
Generate: src/<files>, tests/<files>, package.json, README.md
|
|
51
|
+
Commits: one per logical group.
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Phase 3: Integration (sequential)
|
|
55
|
+
|
|
56
|
+
1. Verify cross-package imports
|
|
57
|
+
2. `docker compose build`
|
|
58
|
+
3. `docker compose up -d`
|
|
59
|
+
4. Database migration (if applicable): `npx prisma migrate dev` (or equivalent)
|
|
60
|
+
5. Health check: `curl localhost:<port>/health`
|
|
61
|
+
6. Run tests
|
|
62
|
+
7. Git commit: `chore: verify docker integration`
|
|
63
|
+
|
|
64
|
+
### Phase 4: Finalize
|
|
65
|
+
|
|
66
|
+
1. Generate/update `README.md` with quick start
|
|
67
|
+
2. `git tag v0.1.0-scaffold`
|
|
68
|
+
3. Report summary
|
|
69
|
+
|
|
70
|
+
## Flags
|
|
71
|
+
|
|
72
|
+
- `--skip-tests` — don't generate test files (NOT recommended)
|
|
73
|
+
- `--skip-seed` — skip DB seeding
|
|
74
|
+
- `--dry-run` — show plan without executing
|
|
75
|
+
|
|
76
|
+
## Critical Rules
|
|
77
|
+
|
|
78
|
+
1. **Docs as source of truth** — every file references specific docs, never memory
|
|
79
|
+
2. **Maximize parallelism** — independent packages run as parallel Tasks
|
|
80
|
+
3. **Atomic commits** — one commit per logical change
|
|
81
|
+
4. **Full integration** — Phase 3 includes build + start + health check
|
|
82
|
+
5. **Project-specific** — adapt all examples to actual tech stack
|
|
83
|
+
|
|
84
|
+
## Related
|
|
85
|
+
|
|
86
|
+
- `/replicate` — generates the SPARC docs that `/start` reads
|
|
87
|
+
- `/run mvp` — builds features after scaffold is up
|
|
88
|
+
- `/feature <name>` — implement individual features
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Stop hook — auto-commits .claude/insights/ if anything changed.
|
|
6
|
+
* Cross-platform: pure Node + execFileSync, no shell.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const fs = require('node:fs');
|
|
10
|
+
const path = require('node:path');
|
|
11
|
+
const { execFileSync } = require('node:child_process');
|
|
12
|
+
|
|
13
|
+
const TARGET_DIR = path.resolve(process.cwd(), '.claude', 'insights');
|
|
14
|
+
const RELATIVE = path.relative(process.cwd(), TARGET_DIR);
|
|
15
|
+
const SILENT = { stdio: 'ignore' };
|
|
16
|
+
|
|
17
|
+
function git(args) {
|
|
18
|
+
return execFileSync('git', args, SILENT);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
if (!fs.existsSync(TARGET_DIR)) process.exit(0);
|
|
23
|
+
try { git(['rev-parse', '--git-dir']); } catch { process.exit(0); }
|
|
24
|
+
|
|
25
|
+
git(['add', '--', RELATIVE]);
|
|
26
|
+
|
|
27
|
+
let hasDiff = false;
|
|
28
|
+
try {
|
|
29
|
+
git(['diff', '--cached', '--quiet', '--', RELATIVE]);
|
|
30
|
+
} catch {
|
|
31
|
+
hasDiff = true;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (hasDiff) {
|
|
35
|
+
git(['commit', '--only', '--', RELATIVE, '-m', 'docs(insights): auto-capture']);
|
|
36
|
+
}
|
|
37
|
+
} catch (_err) {
|
|
38
|
+
process.exit(0);
|
|
39
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Stop hook — auto-commits docs/plans/ if anything changed.
|
|
6
|
+
* Cross-platform: pure Node + execFileSync, no shell.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const fs = require('node:fs');
|
|
10
|
+
const path = require('node:path');
|
|
11
|
+
const { execFileSync } = require('node:child_process');
|
|
12
|
+
|
|
13
|
+
const TARGET_DIR = path.resolve(process.cwd(), 'docs', 'plans');
|
|
14
|
+
const RELATIVE = path.relative(process.cwd(), TARGET_DIR);
|
|
15
|
+
const SILENT = { stdio: 'ignore' };
|
|
16
|
+
|
|
17
|
+
function git(args) {
|
|
18
|
+
return execFileSync('git', args, SILENT);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
if (!fs.existsSync(TARGET_DIR)) process.exit(0);
|
|
23
|
+
try { git(['rev-parse', '--git-dir']); } catch { process.exit(0); }
|
|
24
|
+
|
|
25
|
+
git(['add', '--', RELATIVE]);
|
|
26
|
+
|
|
27
|
+
let hasDiff = false;
|
|
28
|
+
try {
|
|
29
|
+
git(['diff', '--cached', '--quiet', '--', RELATIVE]);
|
|
30
|
+
} catch {
|
|
31
|
+
hasDiff = true;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (hasDiff) {
|
|
35
|
+
git(['commit', '--only', '--', RELATIVE, '-m', 'docs(plan): auto-save']);
|
|
36
|
+
}
|
|
37
|
+
} catch (_err) {
|
|
38
|
+
process.exit(0);
|
|
39
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Stop hook — auto-commits .claude/feature-roadmap.json if it changed.
|
|
6
|
+
* Cross-platform: uses execFileSync('git', [...]) so no shell quoting/redirect issues.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const fs = require('node:fs');
|
|
10
|
+
const path = require('node:path');
|
|
11
|
+
const { execFileSync } = require('node:child_process');
|
|
12
|
+
|
|
13
|
+
const TARGET = path.resolve(process.cwd(), '.claude', 'feature-roadmap.json');
|
|
14
|
+
const RELATIVE = path.relative(process.cwd(), TARGET);
|
|
15
|
+
const SILENT = { stdio: 'ignore' };
|
|
16
|
+
|
|
17
|
+
function git(args) {
|
|
18
|
+
return execFileSync('git', args, SILENT);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
// Skip if file doesn't exist or we're not in a git repo.
|
|
23
|
+
if (!fs.existsSync(TARGET)) process.exit(0);
|
|
24
|
+
try { git(['rev-parse', '--git-dir']); } catch { process.exit(0); }
|
|
25
|
+
|
|
26
|
+
// Stage only the target file.
|
|
27
|
+
git(['add', '--', RELATIVE]);
|
|
28
|
+
|
|
29
|
+
// Check whether anything is staged for THIS path.
|
|
30
|
+
// `git diff --cached --quiet -- <path>` exits 0 = no diff, 1 = diff exists.
|
|
31
|
+
let hasDiff = false;
|
|
32
|
+
try {
|
|
33
|
+
git(['diff', '--cached', '--quiet', '--', RELATIVE]);
|
|
34
|
+
} catch {
|
|
35
|
+
hasDiff = true;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (hasDiff) {
|
|
39
|
+
git(['commit', '--only', '--', RELATIVE, '-m', 'docs(roadmap): auto-update']);
|
|
40
|
+
}
|
|
41
|
+
} catch (_err) {
|
|
42
|
+
// Best-effort — never break Claude session on commit failures.
|
|
43
|
+
process.exit(0);
|
|
44
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* SessionStart hook — injects up to 3 most recent insights from
|
|
6
|
+
* .claude/insights/index.md into Claude's initial session context (via stdout).
|
|
7
|
+
*
|
|
8
|
+
* Cross-platform: pure Node, no shell pipes. Silent on missing index.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const fs = require('node:fs');
|
|
12
|
+
const path = require('node:path');
|
|
13
|
+
|
|
14
|
+
const INDEX = path.resolve(process.cwd(), '.claude', 'insights', 'index.md');
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
if (!fs.existsSync(INDEX)) process.exit(0);
|
|
18
|
+
const text = fs.readFileSync(INDEX, 'utf8');
|
|
19
|
+
// Each insight starts with "## " heading (per insights-capture.md convention).
|
|
20
|
+
const sections = text.split(/^## /m).filter(Boolean);
|
|
21
|
+
const recent = sections.slice(-3).map((s) => '## ' + s.trim()).join('\n\n');
|
|
22
|
+
if (recent.length > 0) {
|
|
23
|
+
process.stdout.write('## Recent project insights\n\n' + recent + '\n');
|
|
24
|
+
}
|
|
25
|
+
} catch (_err) {
|
|
26
|
+
// Hook is advisory — never block the session on errors here.
|
|
27
|
+
process.exit(0);
|
|
28
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* state-update.cjs (v1.5.0) — write `.claude/.p-replicator-state.json`.
|
|
6
|
+
*
|
|
7
|
+
* Used by pipeline commands (/run, /go, /feature, /replicate) to publish
|
|
8
|
+
* current command + phase + progress for the statusline to display.
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* node .claude/hooks/state-update.cjs \
|
|
12
|
+
* --command /feature \
|
|
13
|
+
* --phase VALIDATE \
|
|
14
|
+
* --index 2 \
|
|
15
|
+
* --total 4 \
|
|
16
|
+
* --progress 0.5 \
|
|
17
|
+
* [--last-command /replicate] \
|
|
18
|
+
* [--last-feature auth-jwt]
|
|
19
|
+
*
|
|
20
|
+
* Or pass JSON in one arg:
|
|
21
|
+
* node .claude/hooks/state-update.cjs --json '{"currentCommand":"/run", ...}'
|
|
22
|
+
*
|
|
23
|
+
* Idempotent: overwrites the state file each call. Always exits 0 (best-effort).
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
const fs = require('node:fs');
|
|
27
|
+
const path = require('node:path');
|
|
28
|
+
|
|
29
|
+
const STATE_FILE = path.resolve(process.cwd(), '.claude', '.p-replicator-state.json');
|
|
30
|
+
|
|
31
|
+
function parseArgs(argv) {
|
|
32
|
+
const out = {};
|
|
33
|
+
for (let i = 2; i < argv.length; i++) {
|
|
34
|
+
const a = argv[i];
|
|
35
|
+
if (!a.startsWith('--')) continue;
|
|
36
|
+
const key = a.slice(2);
|
|
37
|
+
const next = argv[i + 1];
|
|
38
|
+
if (next && !next.startsWith('--')) {
|
|
39
|
+
out[key] = next;
|
|
40
|
+
i++;
|
|
41
|
+
} else {
|
|
42
|
+
out[key] = true;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return out;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function main() {
|
|
49
|
+
try {
|
|
50
|
+
const args = parseArgs(process.argv);
|
|
51
|
+
|
|
52
|
+
let state;
|
|
53
|
+
if (args.json) {
|
|
54
|
+
try { state = JSON.parse(args.json); }
|
|
55
|
+
catch { state = {}; }
|
|
56
|
+
} else {
|
|
57
|
+
state = {
|
|
58
|
+
currentCommand: args.command || null,
|
|
59
|
+
currentPhase: args.phase ? {
|
|
60
|
+
name: args.phase,
|
|
61
|
+
index: args.index ? parseInt(args.index, 10) : null,
|
|
62
|
+
total: args.total ? parseInt(args.total, 10) : null,
|
|
63
|
+
progress: args.progress ? parseFloat(args.progress) : null,
|
|
64
|
+
} : null,
|
|
65
|
+
lastCommand: args['last-command'] || null,
|
|
66
|
+
lastFeature: args['last-feature'] || null,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
state.updatedAt = new Date().toISOString();
|
|
71
|
+
|
|
72
|
+
fs.mkdirSync(path.dirname(STATE_FILE), { recursive: true });
|
|
73
|
+
fs.writeFileSync(STATE_FILE, JSON.stringify(state, null, 2) + '\n', 'utf8');
|
|
74
|
+
} catch (_err) {
|
|
75
|
+
// Best-effort. Never fail the calling pipeline on state-write issues.
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
main();
|