@chief-clancy/plan 0.4.0 → 0.4.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chief-clancy/plan",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "private": false,
5
5
  "description": "Implementation planner for Claude Code — decompose briefs into actionable plans",
6
6
  "author": "Alex Clapperton",
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## Overview
4
4
 
5
- Fetch backlog tickets from the board, explore the codebase, and generate structured implementation plans. In board mode, plans are posted as comments on the ticket for human review. With `--from`, plans from local brief files are saved to `.clancy/plans/`, with an optional board comment when credentials are available. Does not implement anything — planning only. In **terminal mode**, use `/clancy:approve-plan` to promote plans. In **standalone mode** or **standalone+board mode**, install the full pipeline (`npx chief-clancy`) to promote plans.
5
+ Fetch backlog tickets from the board, explore the codebase, and generate structured implementation plans. In board mode, plans are posted as comments on the ticket for human review. With `--from`, plans from local brief files are saved to `.clancy/plans/`, with an optional board comment when credentials are available. Does not implement anything — planning only. Use `/clancy:approve-plan` to approve plans in any install mode it ships with this package, writes a local `.approved` marker for plan-file stems, and runs the board transport flow for ticket keys only when board credentials are available.
6
6
 
7
7
  ---
8
8
 
@@ -24,7 +24,7 @@ Check for `.clancy/.env`:
24
24
  If `.clancy/.env` is present, check for `.clancy/clancy-implement.js`:
25
25
 
26
26
  - **Present** → **terminal mode**. Full Clancy pipeline installed.
27
- - **Absent** → **standalone+board mode**. Board credentials available via `/clancy:board-setup`. Board ticket mode works. Step 5 works. But `/clancy:approve-plan` is not available (requires full pipeline).
27
+ - **Absent** → **standalone+board mode**. Board credentials available via `/clancy:board-setup`. Board ticket mode works. Step 5 works. `/clancy:approve-plan` also works in this mode — it ships with the plan package and runs the board transport flow for ticket keys or writes a local `.approved` marker for plan-file stems.
28
28
 
29
29
  ### 2. Terminal-mode preflight (skip in standalone mode and standalone+board mode)
30
30
 
@@ -822,7 +822,7 @@ Write the plan in this exact template:
822
822
 
823
823
  ---
824
824
 
825
- _Generated by [Clancy](https://github.com/Pushedskydiver/chief-clancy). To request changes: comment on this ticket, then re-run `/clancy:plan` to revise. To start over: `/clancy:plan --fresh`. To approve: `/clancy:approve-plan {KEY}` (requires full pipeline — `npx chief-clancy`)._
825
+ _Generated by [Clancy](https://github.com/Pushedskydiver/chief-clancy). To request changes: comment on this ticket, then re-run `/clancy:plan` to revise. To start over: `/clancy:plan --fresh`. To approve: `/clancy:approve-plan {KEY}`._
826
826
  ```
827
827
 
828
828
  **If re-planning with feedback**, prepend a section before Summary:
@@ -885,7 +885,7 @@ Replace the `**Ticket:** [{KEY}] {Title}` line from the board template with `**S
885
885
  **Local plan footer:** Replace the board-specific footer with:
886
886
 
887
887
  ```
888
- _Generated by [Clancy](https://github.com/Pushedskydiver/chief-clancy). To request changes: add a ## Feedback section to this file, then re-run `/clancy:plan --from {path}` to revise. To start over: `/clancy:plan --fresh --from {path}`. To approve: install the full pipeline — npx chief-clancy._
888
+ _Generated by [Clancy](https://github.com/Pushedskydiver/chief-clancy). To request changes: add a ## Feedback section to this file, then re-run `/clancy:plan --from {path}` to revise. To start over: `/clancy:plan --fresh --from {path}`. To approve: `/clancy:approve-plan {plan-id}`._
889
889
  ```
890
890
 
891
891
  **Re-planning:** If `--fresh` was used, the existing plan file is overwritten (same slug + row number = same filename).
@@ -1052,8 +1052,7 @@ Planned {N} ticket(s):
1052
1052
  ⏭️ [{KEY4}] {Title} — not a codebase change
1053
1053
 
1054
1054
  Plans written to your board. After review:
1055
- Terminal mode: /clancy:approve-plan {KEY}
1056
- Standalone: install the full pipeline — npx chief-clancy
1055
+ /clancy:approve-plan {KEY}
1057
1056
 
1058
1057
  "Let me dust this for prints..."
1059
1058
  ```
@@ -1068,7 +1067,7 @@ Single row:
1068
1067
 
1069
1068
  ✅ Saved to .clancy/plans/{slug}-{row-number}.md
1070
1069
 
1071
- To approve: install the full pipeline — npx chief-clancy
1070
+ To approve: /clancy:approve-plan {slug}-{row-number}
1072
1071
 
1073
1072
  "Let me dust this for prints..."
1074
1073
  ```
@@ -1083,7 +1082,7 @@ Multi-row (`--afk`):
1083
1082
  ✅ Row 2: {title} — Saved to .clancy/plans/{slug}-2.md
1084
1083
  ⏭️ Row 3: {title} — already planned
1085
1084
 
1086
- To approve: install the full pipeline npx chief-clancy
1085
+ To approve: /clancy:approve-plan {slug}-{row-number} (or use /clancy:plan --list to see all plans)
1087
1086
 
1088
1087
  "Let me dust this for prints..."
1089
1088
  ```
@@ -1101,35 +1100,47 @@ Scan `.clancy/plans/` for all `.md` files. For each file, parse the local plan h
1101
1100
  - **Row** — value of the `**Row:**` line (e.g. `#2 — Add toggle component`). Display `?` if absent or empty.
1102
1101
  - **Source** — value of the `**Source:**` line (the brief's Source field). Display `?` if absent or empty.
1103
1102
  - **Planned** — value of the `**Planned:**` line (the YYYY-MM-DD planned date). Display `?` if absent or unparseable as a date.
1104
- - **Status** — for now this is always `Planned`. Reserved for `/clancy:approve-plan` (a future PR) which will write a sibling `.approved` marker file (e.g. `.clancy/plans/add-dark-mode-2.approved`). When that marker exists for a given plan, Status becomes `Approved`. The column is included in the listing today so the format will not change when approval markers ship.
1103
+ - **Status** — derived live from the sibling `.approved` marker written by `/clancy:approve-plan` (PR 7b). The reader is filesystem-only no `.clancy/.env` or board access required. For each plan file, follow this procedure (every numbered step either branches to a verdict or feeds the next step):
1104
+ 1. Check whether `.clancy/plans/{plan-id}.approved` exists. If marker absent → `Planned` (verdict)
1105
+ 2. Marker exists. Read and validate its `sha256=` line (the marker body is two `key=value` lines: `sha256={hex}` and `approved_at={ISO 8601}`) AND hash the current plan file's bytes the same way `/clancy:approve-plan` Step 4a does (lowercase hex SHA-256, no normalisation, no line-ending fix)
1106
+ 3. If the marker exists but is malformed, missing its `sha256=` line, has a non-hex or wrong-length `sha256` value, or otherwise cannot be parsed deterministically → `Stale (re-approve)` (verdict). Print a hint after the table: `Marker .clancy/plans/{plan-id}.approved is malformed. Delete it and re-run /clancy:approve-plan {plan-id} to recreate.` Folding this into `Stale` (rather than inventing a new state) keeps the inventory deterministic — the user's remediation is the same as for a hash drift: delete the marker and re-approve
1107
+ 4. If the marker's valid `sha256` matches the current plan file's hash → `Approved` (verdict)
1108
+ 5. If the marker exists and its valid `sha256` differs from the current hash → `Stale (re-approve)` (verdict) — the plan file was edited after approval. `/clancy:implement-from` (PR 8) will refuse to run against a stale plan until it is re-approved
1109
+
1110
+ A future `Implemented` state — derived from `LOCAL_IMPLEMENT` entries in `.clancy/progress.txt` — will be added by PR 8. Today the inventory shows three states (`Planned`, `Approved`, `Stale (re-approve)`), with malformed `.approved` markers folded into `Stale (re-approve)`; the table format is stable so PR 8's addition will be a one-line extension.
1105
1111
 
1106
1112
  A field is considered missing if the line is absent or its value is empty after the colon. Plans missing all expected fields are still listed (with `?` placeholders) so the user can find and clean them up.
1107
1113
 
1108
1114
  **Sort:** by `**Planned**` date, newest first. Tie-break on same date by Plan ID, alphabetical ascending. Files with a missing or unparseable date sort last (after all dated plans), and tie-break among themselves by Plan ID alphabetical ascending. The sort must be deterministic across runs.
1109
1115
 
1110
- Display:
1116
+ **Summary line:** after the table, print one line in the format `{N} local plan(s). {A} approved, {S} stale, {P} planned.` where the counts match the rows above. Omit zero-count states for brevity (e.g. `3 local plan(s). 2 approved, 1 planned.` if no plans are stale). A `Stale (re-approve)` row means the plan file was edited after approval — re-running `/clancy:approve-plan {plan-id}` (after deleting the existing marker) will refresh the SHA.
1117
+
1118
+ Display the inventory as a pipe-delimited table so each Status value is unambiguous to scan and parse:
1111
1119
 
1112
1120
  ```
1113
1121
  Clancy — Plans
1114
1122
  ================================================================
1115
1123
 
1116
- [1] add-dark-mode-2 2026-04-08 Planned Row #2 Add toggle component Brief: 2026-04-01-add-dark-mode.md Source: #50
1117
- [2] add-dark-mode-1 2026-04-07 Planned Row #1 — Wire theme context Brief: 2026-04-01-add-dark-mode.md Source: #50
1118
- [3] customer-portal-3 2026-04-05 Planned Row #3Billing page Brief: 2026-03-28-customer-portal.md Source: PROJ-200
1124
+ | # | Plan ID | Planned | Status | Row | Brief | Source |
1125
+ |---|--------------------|------------|---------------------|----------------------------------|------------------------------------|-----------|
1126
+ | 1 | add-dark-mode-2 | 2026-04-08 | Approved | #2Add toggle component | 2026-04-01-add-dark-mode.md | #50 |
1127
+ | 2 | add-dark-mode-1 | 2026-04-07 | Stale (re-approve) | #1 — Wire theme context | 2026-04-01-add-dark-mode.md | #50 |
1128
+ | 3 | customer-portal-3 | 2026-04-05 | Planned | #3 — Billing page | 2026-03-28-customer-portal.md | PROJ-200 |
1119
1129
 
1120
- 3 local plan(s).
1130
+ 3 local plan(s). 1 approved, 1 stale, 1 planned.
1121
1131
 
1122
- To revise: add a `## Feedback` section to the plan file, then re-run /clancy:plan --from <brief>.
1123
- To start over: /clancy:plan --fresh --from <brief>.
1124
- To approve: install the full pipeline npx chief-clancy.
1132
+ To approve a plan: /clancy:approve-plan {plan-id}
1133
+ To re-approve a stale plan: delete .clancy/plans/{plan-id}.approved, then /clancy:approve-plan {plan-id}
1134
+ To revise: add a `## Feedback` section to the plan file, then re-run /clancy:plan --from {brief}
1135
+ To start over: /clancy:plan --fresh --from {brief}
1125
1136
  ```
1126
1137
 
1127
- The first column in the listing is the Plan ID (filename without `.md`), not the brief slug.
1138
+ The first column (after `#`) is the Plan ID (filename without `.md`), not the brief slug. Pipe-table format is intentional — Status values like `Stale (re-approve)` contain spaces, and a column-aligned space-delimited layout would make them ambiguous to read.
1128
1139
 
1129
1140
  If `.clancy/plans/` does not exist or contains no `.md` files:
1130
1141
 
1131
1142
  ```
1132
- No plans found. Run /clancy:plan --from .clancy/briefs/<brief>.md to create one.
1143
+ No plans found. Run /clancy:plan --from .clancy/briefs/{brief}.md to create one.
1133
1144
  ```
1134
1145
 
1135
1146
  Stop after display. The `--list` step never logs to `.clancy/progress.txt` and never modifies any file — it is purely a read-only inventory view of the local plans directory.
@@ -595,13 +595,75 @@ describe('plan inventory step', () => {
595
595
  expect(content).toContain(
596
596
  '**Plan ID** — the plan filename minus the `.md` extension',
597
597
  );
598
- expect(content).toContain('first column in the listing is the Plan ID');
598
+ expect(content).toContain('first column (after `#`) is the Plan ID');
599
599
  });
600
600
 
601
- it('reserves a Status column for the future approve-plan PR', () => {
601
+ it('Status column reads sibling .approved marker for live state', () => {
602
602
  expect(content).toContain('**Status**');
603
- expect(content).toContain('always `Planned`');
604
- expect(content).toContain('`.approved` marker');
603
+ expect(content).toContain('sibling `.approved` marker');
604
+ });
605
+
606
+ it('Status is Planned when no marker exists', () => {
607
+ expect(content).toContain('marker absent → `Planned`');
608
+ });
609
+
610
+ it('Status is Approved when marker sha256 matches the current plan file', () => {
611
+ expect(content).toContain('sha256` matches the current plan file');
612
+ expect(content).toContain('→ `Approved`');
613
+ });
614
+
615
+ it('Status is Stale (re-approve) when marker sha256 drifts from the plan file', () => {
616
+ expect(content).toContain('Stale (re-approve)');
617
+ expect(content).toContain('sha256` differs');
618
+ });
619
+
620
+ it('inventory example shows at least one Approved row', () => {
621
+ // Match a pipe-delimited table cell on both sides (literal `|`s with
622
+ // tolerant whitespace) so the test proves the example output is in the
623
+ // pipe-delimited table format, not just that the word `Approved` happens
624
+ // to appear in nearby prose.
625
+ expect(content).toMatch(/\|\s*Approved\s*\|/);
626
+ });
627
+
628
+ it('inventory example shows at least one Stale row', () => {
629
+ expect(content).toMatch(/\|\s*Stale \(re-approve\)\s*\|/);
630
+ });
631
+
632
+ it('inventory example still shows at least one Planned row', () => {
633
+ expect(content).toMatch(/\|\s*Planned\s*\|/);
634
+ });
635
+
636
+ it('inventory documents the summary line format and zero-count omission', () => {
637
+ expect(content).toContain('Summary line');
638
+ expect(content).toContain(
639
+ '{N} local plan(s). {A} approved, {S} stale, {P} planned.',
640
+ );
641
+ expect(content).toContain('Omit zero-count states');
642
+ });
643
+
644
+ it('explains that Stale means the plan was edited after approval', () => {
645
+ expect(content).toContain('plan file was edited after approval');
646
+ });
647
+
648
+ it('reserves an Implemented state for PR 8 without claiming it exists today', () => {
649
+ expect(content).toContain('Implemented');
650
+ expect(content).toContain('PR 8');
651
+ expect(content).toContain('shows three states');
652
+ });
653
+
654
+ it('folds malformed .approved markers into Stale (re-approve)', () => {
655
+ expect(content).toContain('marker exists but is malformed');
656
+ expect(content).toContain('non-hex or wrong-length');
657
+ expect(content).toContain('cannot be parsed deterministically');
658
+ });
659
+
660
+ it('uses {plan-id} placeholder consistently in the footer (no <plan-id>)', () => {
661
+ expect(content).not.toContain('<plan-id>');
662
+ expect(content).toContain('/clancy:approve-plan {plan-id}');
663
+ });
664
+
665
+ it('inventory footer hint points at /clancy:approve-plan', () => {
666
+ expect(content).toContain('/clancy:approve-plan');
605
667
  });
606
668
 
607
669
  it('sort is deterministic with explicit tie-breakers', () => {