claude-launchpad 1.9.0 → 1.10.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.
Files changed (34) hide show
  1. package/README.md +29 -45
  2. package/dist/{chunk-GA3IUQUM.js → chunk-42Q2MAQB.js} +3 -3
  3. package/dist/{chunk-RCYLZUU6.js → chunk-5KQ2JDZN.js} +401 -55
  4. package/dist/chunk-5KQ2JDZN.js.map +1 -0
  5. package/dist/{chunk-72VWDNAE.js → chunk-OWOW5KFX.js} +2 -2
  6. package/dist/{chunk-DYHPVA6O.js → chunk-WVQBG4YR.js} +2 -2
  7. package/dist/{chunk-I4S4Q2IV.js → chunk-ZIEONJBY.js} +2 -2
  8. package/dist/cli.js +86 -42
  9. package/dist/cli.js.map +1 -1
  10. package/dist/commands/memory/server.js +3 -3
  11. package/dist/{context-X7UP2ODK.js → context-QLQLJOR2.js} +5 -5
  12. package/dist/{install-XBCEI5QK.js → install-5XZFLN3C.js} +7 -6
  13. package/dist/{install-XBCEI5QK.js.map → install-5XZFLN3C.js.map} +1 -1
  14. package/dist/{pull-VA62U3OP.js → pull-46YFKQ6S.js} +7 -7
  15. package/dist/{push-C3M6Q4V7.js → push-FMAHNK4U.js} +7 -7
  16. package/dist/{require-deps-UBU5CYM5.js → require-deps-H4SHQWD2.js} +3 -3
  17. package/dist/{stats-R4TWCPHW.js → stats-YGK6PZ3A.js} +6 -6
  18. package/dist/{sync-clean-P4S7V2JS.js → sync-clean-PCR3QCZK.js} +3 -3
  19. package/dist/{sync-status-TPYUF43G.js → sync-status-KZSPPHPY.js} +7 -7
  20. package/dist/{tui-FFLCUR7E.js → tui-XXYVOGJL.js} +4 -4
  21. package/package.json +2 -2
  22. package/dist/chunk-RCYLZUU6.js.map +0 -1
  23. /package/dist/{chunk-GA3IUQUM.js.map → chunk-42Q2MAQB.js.map} +0 -0
  24. /package/dist/{chunk-72VWDNAE.js.map → chunk-OWOW5KFX.js.map} +0 -0
  25. /package/dist/{chunk-DYHPVA6O.js.map → chunk-WVQBG4YR.js.map} +0 -0
  26. /package/dist/{chunk-I4S4Q2IV.js.map → chunk-ZIEONJBY.js.map} +0 -0
  27. /package/dist/{context-X7UP2ODK.js.map → context-QLQLJOR2.js.map} +0 -0
  28. /package/dist/{pull-VA62U3OP.js.map → pull-46YFKQ6S.js.map} +0 -0
  29. /package/dist/{push-C3M6Q4V7.js.map → push-FMAHNK4U.js.map} +0 -0
  30. /package/dist/{require-deps-UBU5CYM5.js.map → require-deps-H4SHQWD2.js.map} +0 -0
  31. /package/dist/{stats-R4TWCPHW.js.map → stats-YGK6PZ3A.js.map} +0 -0
  32. /package/dist/{sync-clean-P4S7V2JS.js.map → sync-clean-PCR3QCZK.js.map} +0 -0
  33. /package/dist/{sync-status-TPYUF43G.js.map → sync-status-KZSPPHPY.js.map} +0 -0
  34. /package/dist/{tui-FFLCUR7E.js.map → tui-XXYVOGJL.js.map} +0 -0
@@ -32,20 +32,20 @@ async function readJsonOrNull(path) {
32
32
  }
33
33
 
34
34
  // src/lib/settings.ts
35
- import { readFile as readFile4, writeFile as writeFile4, mkdir as mkdir3 } from "fs/promises";
36
- import { join as join6 } from "path";
35
+ import { readFile as readFile5, writeFile as writeFile5, mkdir as mkdir4 } from "fs/promises";
36
+ import { join as join7 } from "path";
37
37
 
38
38
  // src/lib/output.ts
39
39
  import chalk from "chalk";
40
40
 
41
41
  // src/commands/doctor/fixer.ts
42
- import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir2, access as access2 } from "fs/promises";
43
- import { join as join5 } from "path";
42
+ import { readFile as readFile4, writeFile as writeFile4, mkdir as mkdir3, access as access2 } from "fs/promises";
43
+ import { join as join6 } from "path";
44
44
  import { homedir } from "os";
45
45
 
46
46
  // src/lib/sections.ts
47
47
  var SESSION_START_CONTENT = "- ALWAYS read @TASKS.md first \u2014 it tracks progress across sessions\n- Check the Session Log at the bottom of TASKS.md for where we left off\n- Update TASKS.md as you complete work";
48
- var BACKLOG_CONTENT = "- When a feature is discussed but deferred, add it to BACKLOG.md immediately\n- Never leave future ideas only in TASKS.md or conversation \u2014 they get lost\n- BACKLOG.md is the single source of truth for parked features";
48
+ var BACKLOG_CONTENT = "- When a feature is discussed but deferred, add it to BACKLOG.md immediately\n- Never leave future ideas only in TASKS.md or conversation \u2014 they get lost\n- BACKLOG.md is the single source of truth for parked features\n- Every WP uses the 7-field template in BACKLOG.md \u2014 no freeform entries\n- Pull a WP into a sprint = **move**, not copy. A WP lives in exactly one file at a time";
49
49
  var STOP_AND_SWARM_CONTENT = "Three failed iterations on the same problem = stop iterating alone.\nOn the fourth attempt, spin up at least 3 parallel agents via the Agent tool, each investigating from a different angle:\n1. Root-cause debug agent\n2. Upstream library/docs research agent\n3. Alternative architecture agent\nWait for all agents to return, synthesize their findings, then act.\nDon't keep guessing in circles \u2014 rotate perspectives.";
50
50
  var OFF_LIMITS_CONTENT = "- Never hardcode secrets \u2014 use environment variables\n- Never write to `.env` files\n- Never expose internal error details in API responses";
51
51
  var SKILL_AUTHORING_CONTENT = 'When creating Claude Code skills (.claude/skills/*/SKILL.md):\n\n- Keep SKILL.md under 500 lines \u2014 move reference material to supporting files in the same directory\n- Front-load description (first 250 chars shown in listings) with TRIGGER when / DO NOT TRIGGER when clauses\n- Add allowed-tools in frontmatter to restrict tool access (e.g. Read, Glob, Grep for read-only skills)\n- Add argument-hint in frontmatter showing the expected input format (use $ARGUMENTS or $0, $1 for dynamic input)\n- Set disable-model-invocation: true for skills with side effects (deploy, send messages)\n- Structure as phases: Research, Plan, Execute, Verify with "Done when:" success criteria per phase\n- Handle edge cases and preconditions before execution';
@@ -350,7 +350,7 @@ DerivedData/
350
350
  }
351
351
 
352
352
  // src/commands/init/generators/skill-enhance.ts
353
- var ENHANCE_SKILL_VERSION = 8;
353
+ var ENHANCE_SKILL_VERSION = 9;
354
354
  function generateEnhanceSkill() {
355
355
  return [
356
356
  "---",
@@ -392,7 +392,7 @@ function generateEnhanceSkill() {
392
392
  "2. **## Architecture** - 3-5 bullets describing codebase shape",
393
393
  "3. **## Conventions** - max 8 key patterns. Overflow to .claude/rules/conventions.md",
394
394
  "4. **## Off-Limits** - max 8 guardrails specific to this project",
395
- "5. **## Memory** - ONLY if agentic-memory is configured in settings.json. Max 6 bullets.",
395
+ "5. **## Memory (agentic-memory)** - ONLY if agentic-memory is configured in settings.json. Max 6 bullets. Use this exact heading so `memory install` stays idempotent.",
396
396
  "6. **## Key Decisions** - only decisions that affect how Claude works in this codebase",
397
397
  "",
398
398
  "7. **Skill Authoring** - if .claude/rules/conventions.md lacks a Skill Authoring section, plan to add one",
@@ -557,6 +557,88 @@ function generateEnhanceSkill() {
557
557
  ].join("\n");
558
558
  }
559
559
 
560
+ // src/commands/init/generators/backlog.ts
561
+ function generateBacklogMd(options) {
562
+ const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
563
+ return `# ${options.name} \u2014 Backlog
564
+
565
+ > **Single source of truth for future work.** Every work package (WP) that's been proposed but not yet started lives here.
566
+ >
567
+ > Rules (see \`.claude/rules/workflow.md\` for the full lifecycle):
568
+ > - Every WP uses the template below \u2014 no freeform entries.
569
+ > - WPs are **moved** to \`TASKS.md\` when pulled into a sprint, not copied. A WP lives in exactly one file at a time.
570
+ > - Priority ordering: **P0 > P1 > P2 > P3**. P0 items sit at the top.
571
+ > - A WP ID is minted when the WP is first added here and never reused.
572
+
573
+ ---
574
+
575
+ ## Priority definitions
576
+
577
+ | Priority | Meaning |
578
+ |---|---|
579
+ | **P0** | Blocks launch or an active sprint. Next sprint or sooner. |
580
+ | **P1** | Important for MVP. Pulled within 2\u20133 sprints. |
581
+ | **P2** | Post-MVP or nice-to-have. Review monthly. |
582
+ | **P3** | Parked idea. Review quarterly; delete if still untouched. |
583
+
584
+ ---
585
+
586
+ ## Work package template (copy this exactly)
587
+
588
+ \`\`\`markdown
589
+ ### WP-NNN \u2014 <short imperative title>
590
+
591
+ - **Priority:** P0 | P1 | P2 | P3
592
+ - **Proposed:** YYYY-MM-DD
593
+ - **Stories / Docs:** links to specs, issues, or "none yet"
594
+ - **Depends on:** WP-MMM (empty if none)
595
+ - **Estimate:** XS (<1h) | S (half-day) | M (1\u20132 days) | L (full sprint) | XL (>1 sprint; must decompose)
596
+ - **Trigger to pull:** What has to be true before this moves into a sprint.
597
+ - **Definition of done:** Exactly what "done" looks like.
598
+
599
+ One-paragraph description. Context, rationale, rough approach if known. Keep it tight.
600
+ \`\`\`
601
+
602
+ **Hard rules for entries:**
603
+ - Title is an imperative ("Add referral banner", "Wire up StoreKit upgrade flow") \u2014 not a noun.
604
+ - \`Trigger to pull\` and \`Definition of done\` are mandatory. A WP without either is incomplete.
605
+ - \`XL\` estimates must be broken into multiple WPs before pulling into a sprint.
606
+ - WP IDs are minted on first entry and never reused. Check the Changelog below for the highest ID before adding a new one.
607
+
608
+ ---
609
+
610
+ ## P0 \u2014 Next sprint
611
+
612
+ <!-- Empty. WPs appear here when promoted from P1, or when a new blocker surfaces. -->
613
+
614
+ ---
615
+
616
+ ## P1 \u2014 Soon (within 2\u20133 sprints)
617
+
618
+ <!-- Empty. Add WPs here as sprints approach. -->
619
+
620
+ ---
621
+
622
+ ## P2 \u2014 Post-MVP / nice-to-have
623
+
624
+ <!-- Empty. Review monthly. -->
625
+
626
+ ---
627
+
628
+ ## P3 \u2014 Parked
629
+
630
+ <!-- Empty. Move P2 items here if they survive 2 quarterly reviews without being pulled. -->
631
+
632
+ ---
633
+
634
+ ## Changelog
635
+
636
+ Capture WP promotions, demotions, and deletions so you can audit backlog drift.
637
+
638
+ - ${today}: Backlog established.
639
+ `;
640
+ }
641
+
560
642
  // src/lib/memory-placement.ts
561
643
  import { select } from "@inquirer/prompts";
562
644
  function hasMemoryPermissions(settings) {
@@ -705,6 +787,55 @@ if [ "$backlog_deletions" -eq 0 ]; then
705
787
  echo ""
706
788
  fi
707
789
 
790
+ exit 0
791
+ `;
792
+ var WORKFLOW_CHECK = `#!/usr/bin/env bash
793
+ # Warns on BACKLOG.md / TASKS.md drift. Non-blocking (always exits 0).
794
+ # 1. Same WP ID present in BOTH BACKLOG.md and TASKS.md (violates move-not-copy).
795
+ # 2. TASKS.md longer than 80 lines.
796
+ # 3. \\\`## Current Sprint\\\` contains more than 15 items.
797
+ # 4. \\\`## Session Log\\\` has more than 3 entries.
798
+
799
+ set -u
800
+ fp="\${TOOL_INPUT_FILE_PATH:-}"
801
+
802
+ # Only act on edits to BACKLOG.md or TASKS.md.
803
+ echo "$fp" | grep -qE '(^|/)(BACKLOG|TASKS)\\.md$' || exit 0
804
+
805
+ warn() { printf '%s\\n' "$*"; }
806
+
807
+ # 1. Duplicate WP IDs across both files.
808
+ if [ -f BACKLOG.md ] && [ -f TASKS.md ]; then
809
+ dupes=$(grep -oE 'WP-[0-9]{3}' BACKLOG.md 2>/dev/null | sort -u | while read -r wp; do
810
+ grep -q "$wp" TASKS.md 2>/dev/null && echo "$wp"
811
+ done)
812
+ if [ -n "$dupes" ]; then
813
+ warn "Workflow bug: WP ID present in BOTH BACKLOG.md and TASKS.md (violates move-not-copy \u2014 see .claude/rules/workflow.md):"
814
+ printf '%s\\n' "$dupes"
815
+ warn "Move each listed WP to exactly one file."
816
+ fi
817
+ fi
818
+
819
+ # 2. TASKS.md length.
820
+ if [ -f TASKS.md ]; then
821
+ tasks_lines=$(wc -l < TASKS.md 2>/dev/null | tr -d ' ')
822
+ if [ "\${tasks_lines:-0}" -gt 80 ]; then
823
+ warn "TASKS.md is $tasks_lines lines \u2014 should stay under 80. Prune Completed Sprints or Session Log."
824
+ fi
825
+
826
+ # 3. Current Sprint size.
827
+ current_count=$(awk '/^## Current/{flag=1; next} /^## /{flag=0} flag' TASKS.md 2>/dev/null | grep -cE '^[[:space:]]*- \\[[ x]\\]' || true)
828
+ if [ "\${current_count:-0}" -gt 15 ]; then
829
+ warn "## Current Sprint has $current_count items \u2014 split the sprint (see .claude/rules/workflow.md)."
830
+ fi
831
+
832
+ # 4. Session Log size.
833
+ log_count=$(awk '/^## Session Log/{flag=1; next} /^## /{flag=0} flag' TASKS.md 2>/dev/null | grep -cE '^- \\*\\*' || true)
834
+ if [ "\${log_count:-0}" -gt 3 ]; then
835
+ warn "## Session Log has $log_count entries \u2014 keep to 3 max."
836
+ fi
837
+ fi
838
+
708
839
  exit 0
709
840
  `;
710
841
  async function writeSprintHygieneScripts(root) {
@@ -718,6 +849,14 @@ async function writeSprintHygieneScripts(root) {
718
849
  await chmod(openPath, 493);
719
850
  return { sizePath, openPath };
720
851
  }
852
+ async function writeWorkflowCheckScript(root) {
853
+ const hooksDir = join2(root, ".claude", "hooks");
854
+ await mkdir(hooksDir, { recursive: true });
855
+ const scriptPath = join2(hooksDir, "workflow-check.sh");
856
+ await writeFile(scriptPath, WORKFLOW_CHECK);
857
+ await chmod(scriptPath, 493);
858
+ return scriptPath;
859
+ }
721
860
 
722
861
  // src/commands/doctor/fixer-sprint.ts
723
862
  var WORKTREE_INCLUDE_TEMPLATE = `# Files copied into git worktrees that Claude Code creates for subagents.
@@ -758,6 +897,211 @@ async function addSprintCompleteNudge(root) {
758
897
  }]
759
898
  }, "Added sprint-complete nudge hook");
760
899
  }
900
+ async function addWorkflowCheckHook(root) {
901
+ await writeWorkflowCheckScript(root);
902
+ return addHookToSettings(root, "PostToolUse", "workflow-check.sh", {
903
+ matcher: "Edit|Write",
904
+ hooks: [{ type: "command", command: "bash .claude/hooks/workflow-check.sh 2>/dev/null; exit 0" }]
905
+ }, "Added workflow-check hook (BACKLOG/TASKS staleness warnings)");
906
+ }
907
+
908
+ // src/commands/doctor/fixer-quality.ts
909
+ import { readFile as readFile2, writeFile as writeFile3, mkdir as mkdir2 } from "fs/promises";
910
+ import { join as join4 } from "path";
911
+
912
+ // src/commands/init/generators/workflow-rule.ts
913
+ var WORKFLOW_RULE_VERSION = 1;
914
+ function generateWorkflowRule() {
915
+ return `---
916
+ paths: ["BACKLOG.md", "TASKS.md"]
917
+ ---
918
+
919
+ # Backlog \u2192 Tasks \u2192 Sprint Workflow Rules
920
+
921
+ <!-- lp-workflow-version: ${WORKFLOW_RULE_VERSION} -->
922
+
923
+ These rules apply whenever editing \`BACKLOG.md\` or \`TASKS.md\`. The workflow is load-bearing \u2014 drift here breaks sprint integrity.
924
+
925
+ ## The single rule that makes everything else work
926
+
927
+ **A work package (WP) lives in exactly one of \`BACKLOG.md\` or \`TASKS.md\` at any time.**
928
+
929
+ - Pulled into a sprint \u2192 move (delete from BACKLOG.md, add to TASKS.md under \`## Current Sprint\`) in a single edit.
930
+ - Sprint closed \u2192 the WP leaves TASKS.md entirely (summarized into \`## Completed Sprints\`) and does not return to the backlog.
931
+ - A WP ID appearing in both files at once = bug. A PostToolUse hook warns.
932
+
933
+ ## WP IDs
934
+
935
+ - Format: \`WP-NNN\` (three digits, zero-padded).
936
+ - Minted on first entry to \`BACKLOG.md\`.
937
+ - Never reused, never renamed.
938
+ - Highest-used ID lives at the end of \`BACKLOG.md ## Changelog\` \u2014 check there before minting a new one.
939
+
940
+ ## BACKLOG.md structure (mandatory sections, in order)
941
+
942
+ 1. Header + rules comment block
943
+ 2. \`## Priority definitions\` table
944
+ 3. \`## Work package template\` (the canonical format)
945
+ 4. \`## P0 \u2014 Next sprint\`
946
+ 5. \`## P1 \u2014 Soon (within 2\u20133 sprints)\`
947
+ 6. \`## P2 \u2014 Post-MVP / nice-to-have\`
948
+ 7. \`## P3 \u2014 Parked\`
949
+ 8. \`## Changelog\`
950
+
951
+ ### Every WP entry must include
952
+
953
+ - \`Priority:\` exactly one of P0/P1/P2/P3
954
+ - \`Proposed:\` ISO date (YYYY-MM-DD)
955
+ - \`Stories / Docs:\` spec references, issues, or empty
956
+ - \`Depends on:\` WP IDs or empty
957
+ - \`Estimate:\` one of XS/S/M/L/XL (XL must be decomposed before pulling)
958
+ - \`Trigger to pull:\` what event promotes this to an active sprint
959
+ - \`Definition of done:\` concrete acceptance criteria
960
+ - One-paragraph description
961
+
962
+ ### Priority discipline
963
+
964
+ - P0 items are for the next sprint. If P0 is empty at sprint start, pull the top P1.
965
+ - P1 \u2192 P0 promotion happens during sprint wrap-up, not mid-sprint.
966
+ - P2 items get a monthly review. P3 items get a quarterly review and are deleted if still untouched.
967
+ - Stale P0 items (>2 weeks without movement) are a flag: either pull or demote.
968
+
969
+ ### Changelog discipline
970
+
971
+ - Every WP promotion, demotion, or deletion gets a one-line entry with the date.
972
+ - A backlog audit that finds no changelog entries for 30+ days = staleness; force a review.
973
+
974
+ ## TASKS.md structure (mandatory sections, in order)
975
+
976
+ 1. Header + rules comment
977
+ 2. \`## Current Sprint\`
978
+ 3. \`## Completed Sprints\`
979
+ 4. \`## Session Log\`
980
+
981
+ ### \`## Current Sprint\` discipline
982
+
983
+ - Contains ONLY the active sprint's WPs. Empty between sprints.
984
+ - Format: \`- [ ] WP-NNN \u2014 short title\` (checkbox + WP ID + title from backlog).
985
+ - Each pulled WP becomes one checklist item. Sub-tasks nest with two-space indent, still checkbox.
986
+ - New ideas that surface mid-sprint go to \`BACKLOG.md\`, never appended here.
987
+
988
+ ### \`## Completed Sprints\` discipline
989
+
990
+ - One line per sprint. Format: \`- **SN**: one-sentence outcome + key metric if any.\`
991
+ - Never a wall of text. Detail lives in git history (or your review file).
992
+
993
+ ### \`## Session Log\` discipline
994
+
995
+ - Max 3 entries, most recent 3 sessions only.
996
+ - Each entry: \`- **YYYY-MM-DD (optional window):** what changed this session + what's next.\`
997
+ - Older entries deleted, not archived \u2014 they're in git history if needed.
998
+
999
+ ### Size discipline
1000
+
1001
+ - Whole file stays under 80 lines.
1002
+ - If \`## Current Sprint\` exceeds 15 checkboxes, the sprint is too big \u2014 split it (move some WPs back to \`BACKLOG.md\` P0).
1003
+
1004
+ ## Sprint lifecycle (the exact edit sequence)
1005
+
1006
+ ### Starting a sprint (one session, one commit)
1007
+
1008
+ 1. Pick top-priority WPs from \`BACKLOG.md\` (P0 first).
1009
+ 2. **Same edit:** delete them from \`BACKLOG.md\`, add them to \`TASKS.md ## Current Sprint\`.
1010
+ 3. Update \`BACKLOG.md ## Changelog\`: \`YYYY-MM-DD: WP-NNN pulled into Sprint SN\`.
1011
+ 4. Write the sprint plan (outline approach, success criteria, tests to add).
1012
+ 5. For hard-TDD surfaces, write the test spec **before** implementation.
1013
+ 6. Commit the pull + plan together: \`chore(sprint-N): pull WP-NNN into sprint + plan\`.
1014
+
1015
+ ### Closing a sprint (one session, one commit)
1016
+
1017
+ 1. All \`## Current Sprint\` items checked off, or explicitly moved back to backlog with rationale.
1018
+ 2. Run your review workflow \u2014 verify typecheck, tests, and convention compliance before declaring done.
1019
+ 3. Add one-line summary to \`## Completed Sprints\`.
1020
+ 4. Empty \`## Current Sprint\` back to the placeholder comment.
1021
+ 5. Update \`## Session Log\` (prune to 3 entries).
1022
+ 6. \`BACKLOG.md ## Changelog\`: \`YYYY-MM-DD: Sprint SN closed. WP-NNN done.\`
1023
+
1024
+ ## What triggers a staleness warning
1025
+
1026
+ A PostToolUse hook fires warnings on these conditions (treat as bugs):
1027
+
1028
+ - A WP ID appears in both \`BACKLOG.md\` and \`TASKS.md\`.
1029
+ - \`TASKS.md\` exceeds 80 lines.
1030
+ - \`## Current Sprint\` has >15 items.
1031
+ - \`## Session Log\` has >3 entries.
1032
+
1033
+ ## Do not
1034
+
1035
+ - Don't append to \`TASKS.md\` without first checking \`BACKLOG.md\` for the WP \u2014 silent duplication breaks the source-of-truth rule.
1036
+ - Don't leave \`## Current Sprint\` populated between sprints. An empty sprint section is how you know the last sprint closed cleanly.
1037
+ - Don't invent ad-hoc WP formats. Use the template or update the template \u2014 never both.
1038
+ - Don't rewrite \`## Completed Sprints\` into prose. One line each. Forever.
1039
+ - Don't put anything in \`TASKS.md\` that hasn't passed through \`BACKLOG.md\` first. Ideas \u2192 backlog \u2192 sprint. No shortcuts.
1040
+ `;
1041
+ }
1042
+
1043
+ // src/commands/doctor/fixer-quality.ts
1044
+ async function createWorkflowRule(root) {
1045
+ const rulesDir = join4(root, ".claude", "rules");
1046
+ const workflowPath = join4(rulesDir, "workflow.md");
1047
+ if (await fileExists(workflowPath)) return false;
1048
+ await mkdir2(rulesDir, { recursive: true });
1049
+ await writeFile3(workflowPath, generateWorkflowRule());
1050
+ log.success("Created .claude/rules/workflow.md (path-scoped BACKLOG/TASKS workflow rules)");
1051
+ return true;
1052
+ }
1053
+ function isMemoryHeading(line) {
1054
+ return /^## Memory( \(agentic-memory\))?\s*$/.test(line);
1055
+ }
1056
+ function findMemoryBlocks(lines) {
1057
+ const blocks = [];
1058
+ for (let i = 0; i < lines.length; i++) {
1059
+ if (!isMemoryHeading(lines[i])) continue;
1060
+ let end = lines.length;
1061
+ for (let j = i + 1; j < lines.length; j++) {
1062
+ if (/^## /.test(lines[j])) {
1063
+ end = j;
1064
+ break;
1065
+ }
1066
+ }
1067
+ blocks.push({ startIdx: i, endIdx: end, tagged: lines[i].includes("(agentic-memory)") });
1068
+ i = end - 1;
1069
+ }
1070
+ return blocks;
1071
+ }
1072
+ async function collapseMemoryHeadings(root) {
1073
+ const claudeMdPath = join4(root, "CLAUDE.md");
1074
+ let content;
1075
+ try {
1076
+ content = await readFile2(claudeMdPath, "utf-8");
1077
+ } catch {
1078
+ return false;
1079
+ }
1080
+ const lines = content.split("\n");
1081
+ const blocks = findMemoryBlocks(lines);
1082
+ if (blocks.length <= 1) return false;
1083
+ const keeper = blocks.find((b) => b.tagged) ?? blocks[0];
1084
+ const droppedByStart = new Map(
1085
+ blocks.filter((b) => b !== keeper).map((b) => [b.startIdx, b])
1086
+ );
1087
+ const kept = [];
1088
+ let skipUntil = -1;
1089
+ for (let i = 0; i < lines.length; i++) {
1090
+ if (i < skipUntil) continue;
1091
+ const dropped = droppedByStart.get(i);
1092
+ if (dropped) {
1093
+ skipUntil = dropped.endIdx;
1094
+ continue;
1095
+ }
1096
+ kept.push(lines[i]);
1097
+ }
1098
+ const canonical = kept.map(
1099
+ (line) => /^## Memory\s*$/.test(line) ? "## Memory (agentic-memory)" : line
1100
+ );
1101
+ await writeFile3(claudeMdPath, canonical.join("\n"));
1102
+ log.success(`Collapsed ${blocks.length - 1} duplicate ## Memory section(s) in CLAUDE.md`);
1103
+ return true;
1104
+ }
761
1105
 
762
1106
  // src/commands/doctor/fixer-hooks.ts
763
1107
  var FORMATTERS = {
@@ -814,8 +1158,8 @@ async function addSessionStartHook(root) {
814
1158
  }
815
1159
 
816
1160
  // src/commands/doctor/fixer-memory.ts
817
- import { readFile as readFile2 } from "fs/promises";
818
- import { join as join4 } from "path";
1161
+ import { readFile as readFile3 } from "fs/promises";
1162
+ import { join as join5 } from "path";
819
1163
  async function addPlacementHook(root, placement, event, dedupKeyword, entry, prepend, successMsg) {
820
1164
  const read = placement === "local" ? readSettingsLocalJson : readSettingsJson;
821
1165
  const write = placement === "local" ? writeSettingsLocalJson : writeSettingsJson;
@@ -969,9 +1313,9 @@ async function addAllowedMcpServers(root, placement) {
969
1313
  if (settingsServers && typeof settingsServers === "object") {
970
1314
  for (const name of Object.keys(settingsServers)) serverNames.add(name);
971
1315
  }
972
- const mcpJsonPath = join4(root, ".mcp.json");
1316
+ const mcpJsonPath = join5(root, ".mcp.json");
973
1317
  try {
974
- const mcpJson = JSON.parse(await readFile2(mcpJsonPath, "utf-8"));
1318
+ const mcpJson = JSON.parse(await readFile3(mcpJsonPath, "utf-8"));
975
1319
  const mcpServers = mcpJson.mcpServers;
976
1320
  if (mcpServers && typeof mcpServers === "object") {
977
1321
  for (const name of Object.keys(mcpServers)) serverNames.add(name);
@@ -1032,8 +1376,10 @@ var FIX_TABLE = [
1032
1376
  { analyzer: "Quality", match: "Session Start", fix: (root) => addClaudeMdSection(root, "## Session Start", wrapStub(SESSION_START_CONTENT)) },
1033
1377
  { analyzer: "Quality", match: "Backlog", fix: (root) => addClaudeMdSection(root, "## Backlog", wrapStub(BACKLOG_CONTENT)) },
1034
1378
  { analyzer: "Quality", match: "Stop-and-Swarm", fix: (root) => addClaudeMdSection(root, "## Stop-and-Swarm", wrapStub(STOP_AND_SWARM_CONTENT)) },
1379
+ { analyzer: "Quality", match: "Duplicate ## Memory", fix: (root) => collapseMemoryHeadings(root) },
1035
1380
  { analyzer: "Rules", match: "No BACKLOG.md", fix: (root) => createBacklogMd(root) },
1036
1381
  { analyzer: "Rules", match: "No .claudeignore", fix: (root, detected) => createClaudeignore(root, detected) },
1382
+ { analyzer: "Rules", match: "No .claude/rules/workflow.md", fix: (root) => createWorkflowRule(root) },
1037
1383
  { analyzer: "Rules", match: "No .claude/rules/", fix: (root) => createStarterRules(root) },
1038
1384
  { analyzer: "Hooks", match: "PostCompact", fix: (root) => addPostCompactHook(root) },
1039
1385
  { analyzer: "Permissions", match: "force-push", fix: (root) => addForcePushProtection(root) },
@@ -1045,6 +1391,7 @@ var FIX_TABLE = [
1045
1391
  { analyzer: "Hooks", match: "sprint-size-check", fix: (root) => addSprintSizeHook(root) },
1046
1392
  { analyzer: "Hooks", match: "sprint-open-check", fix: (root) => addSprintOpenHook(root) },
1047
1393
  { analyzer: "Hooks", match: "sprint-complete nudge", fix: (root) => addSprintCompleteNudge(root) },
1394
+ { analyzer: "Hooks", match: "workflow-check.sh", fix: (root) => addWorkflowCheckHook(root) },
1048
1395
  { analyzer: "Rules", match: "No skill authoring conventions", fix: (root) => addSkillAuthoringConventions(root) },
1049
1396
  { analyzer: "Rules", match: "No /lp-enhance skill", fix: (root) => createEnhanceSkill(root) },
1050
1397
  { analyzer: "Rules", match: "lp-enhance skill is outdated", fix: (root) => updateEnhanceSkill(root) },
@@ -1060,7 +1407,7 @@ var FIX_TABLE = [
1060
1407
  { analyzer: "Memory", match: "SessionEnd push hook is not nohup-wrapped", fix: (root) => upgradeStaleSessionEndPushHook(root) },
1061
1408
  { analyzer: "Memory", match: "CLAUDE.md missing memory guidance", fix: (root, _det, placement) => {
1062
1409
  const content = "Use agentic-memory to persist knowledge across sessions:\n- Memories are automatically injected at session start\n- STORE IMMEDIATELY when: a dependency strategy changes, an architecture decision is made, a convention is established, a bug pattern is discovered, or a feature is killed/added\n- Use memory_search before memory_store to check for duplicates\n- NEVER store credentials, API keys, tokens, or secrets in memories";
1063
- const target = placement === "local" ? join5(root, ".claude", "CLAUDE.md") : void 0;
1410
+ const target = placement === "local" ? join6(root, ".claude", "CLAUDE.md") : void 0;
1064
1411
  return addClaudeMdSection(root, "## Memory", wrapStub(content), target);
1065
1412
  } }
1066
1413
  ];
@@ -1117,27 +1464,27 @@ async function removeSandboxSettings(root) {
1117
1464
  return true;
1118
1465
  }
1119
1466
  async function addEnvToClaudeignore(root) {
1120
- const ignorePath = join5(root, ".claudeignore");
1467
+ const ignorePath = join6(root, ".claudeignore");
1121
1468
  let content;
1122
1469
  try {
1123
- content = await readFile3(ignorePath, "utf-8");
1470
+ content = await readFile4(ignorePath, "utf-8");
1124
1471
  } catch {
1125
1472
  return false;
1126
1473
  }
1127
1474
  const lines = content.split("\n").map((l) => l.trim());
1128
1475
  if (lines.some((l) => l === ".env" || l === ".env.*" || l === ".env*")) return false;
1129
- await writeFile3(ignorePath, content.trimEnd() + "\n.env\n.env.*\n");
1476
+ await writeFile4(ignorePath, content.trimEnd() + "\n.env\n.env.*\n");
1130
1477
  log.success("Added .env to .claudeignore");
1131
1478
  return true;
1132
1479
  }
1133
1480
  async function addClaudeMdSection(root, heading, content, targetPath) {
1134
- const claudeMdPath = targetPath ?? join5(root, "CLAUDE.md");
1481
+ const claudeMdPath = targetPath ?? join6(root, "CLAUDE.md");
1135
1482
  let existing;
1136
1483
  try {
1137
- existing = await readFile3(claudeMdPath, "utf-8");
1484
+ existing = await readFile4(claudeMdPath, "utf-8");
1138
1485
  } catch {
1139
1486
  if (!targetPath) return false;
1140
- await mkdir2(join5(root, ".claude"), { recursive: true });
1487
+ await mkdir3(join6(root, ".claude"), { recursive: true });
1141
1488
  existing = "# Local Claude Config\n";
1142
1489
  }
1143
1490
  if (existing.includes(heading)) return false;
@@ -1149,36 +1496,32 @@ ${content}
1149
1496
 
1150
1497
  `;
1151
1498
  const updated = existing.slice(0, insertAt) + section + existing.slice(insertAt);
1152
- await writeFile3(claudeMdPath, updated);
1499
+ await writeFile4(claudeMdPath, updated);
1153
1500
  const label = targetPath ? ".claude/CLAUDE.md" : "CLAUDE.md";
1154
1501
  log.success(`Added "${heading}" section to ${label}`);
1155
1502
  return true;
1156
1503
  }
1157
1504
  async function createBacklogMd(root) {
1158
- const backlogPath = join5(root, "BACKLOG.md");
1505
+ const backlogPath = join6(root, "BACKLOG.md");
1159
1506
  try {
1160
1507
  await access2(backlogPath);
1161
1508
  return false;
1162
1509
  } catch {
1163
1510
  }
1164
1511
  const name = root.split("/").pop() ?? "Project";
1165
- await writeFile3(backlogPath, `# ${name} - Backlog
1166
-
1167
- > Features discussed but deferred. Pick up when relevant.
1168
- > Priority: P0 = next sprint, P1 = soon, P2 = when relevant.
1169
- `);
1170
- log.success("Generated BACKLOG.md");
1512
+ await writeFile4(backlogPath, generateBacklogMd({ name, description: "" }));
1513
+ log.success("Generated BACKLOG.md (WP template + P0\u2013P3 sections + changelog)");
1171
1514
  return true;
1172
1515
  }
1173
1516
  async function createClaudeignore(root, detected) {
1174
- const ignorePath = join5(root, ".claudeignore");
1517
+ const ignorePath = join6(root, ".claudeignore");
1175
1518
  try {
1176
1519
  await access2(ignorePath);
1177
1520
  return false;
1178
1521
  } catch {
1179
1522
  }
1180
1523
  const content = generateClaudeignore(detected);
1181
- await writeFile3(ignorePath, content);
1524
+ await writeFile4(ignorePath, content);
1182
1525
  log.success("Generated .claudeignore with language-specific ignore patterns");
1183
1526
  return true;
1184
1527
  }
@@ -1188,15 +1531,15 @@ var SKILL_AUTHORING_SECTION = `
1188
1531
  ${SKILL_AUTHORING_CONTENT}
1189
1532
  `;
1190
1533
  async function createStarterRules(root) {
1191
- const rulesDir = join5(root, ".claude", "rules");
1534
+ const rulesDir = join6(root, ".claude", "rules");
1192
1535
  try {
1193
1536
  await access2(rulesDir);
1194
1537
  return false;
1195
1538
  } catch {
1196
1539
  }
1197
- await mkdir2(rulesDir, { recursive: true });
1198
- await writeFile3(
1199
- join5(rulesDir, "conventions.md"),
1540
+ await mkdir3(rulesDir, { recursive: true });
1541
+ await writeFile4(
1542
+ join6(rulesDir, "conventions.md"),
1200
1543
  `# Project Conventions
1201
1544
 
1202
1545
  - Use conventional commits (feat:, fix:, docs:, refactor:, test:, chore:)
@@ -1209,36 +1552,36 @@ ${SKILL_AUTHORING_SECTION}`
1209
1552
  return true;
1210
1553
  }
1211
1554
  async function addSkillAuthoringConventions(root) {
1212
- const conventionsPath = join5(root, ".claude", "rules", "conventions.md");
1555
+ const conventionsPath = join6(root, ".claude", "rules", "conventions.md");
1213
1556
  let content;
1214
1557
  try {
1215
- content = await readFile3(conventionsPath, "utf-8");
1558
+ content = await readFile4(conventionsPath, "utf-8");
1216
1559
  } catch {
1217
1560
  return false;
1218
1561
  }
1219
1562
  if (/^##\s+Skill\s+Authoring/im.test(content)) return false;
1220
- await writeFile3(conventionsPath, content.trimEnd() + "\n" + SKILL_AUTHORING_SECTION);
1563
+ await writeFile4(conventionsPath, content.trimEnd() + "\n" + SKILL_AUTHORING_SECTION);
1221
1564
  log.success("Added Skill Authoring section to .claude/rules/conventions.md");
1222
1565
  return true;
1223
1566
  }
1224
1567
  async function createEnhanceSkill(root) {
1225
- const skillDir = join5(root, ".claude", "skills", "lp-enhance");
1226
- const skillPath = join5(skillDir, "SKILL.md");
1227
- const globalPath = join5(homedir(), ".claude", "skills", "lp-enhance", "SKILL.md");
1228
- const legacyProject = join5(root, ".claude", "commands", "lp-enhance.md");
1229
- const legacyGlobal = join5(homedir(), ".claude", "commands", "lp-enhance.md");
1568
+ const skillDir = join6(root, ".claude", "skills", "lp-enhance");
1569
+ const skillPath = join6(skillDir, "SKILL.md");
1570
+ const globalPath = join6(homedir(), ".claude", "skills", "lp-enhance", "SKILL.md");
1571
+ const legacyProject = join6(root, ".claude", "commands", "lp-enhance.md");
1572
+ const legacyGlobal = join6(homedir(), ".claude", "commands", "lp-enhance.md");
1230
1573
  if (await fileExists(skillPath) || await fileExists(globalPath) || await fileExists(legacyProject) || await fileExists(legacyGlobal)) return false;
1231
- await mkdir2(skillDir, { recursive: true });
1232
- await writeFile3(skillPath, generateEnhanceSkill());
1574
+ await mkdir3(skillDir, { recursive: true });
1575
+ await writeFile4(skillPath, generateEnhanceSkill());
1233
1576
  log.success("Generated /lp-enhance skill (.claude/skills/lp-enhance/)");
1234
1577
  return true;
1235
1578
  }
1236
1579
  async function updateEnhanceSkill(root) {
1237
- const projectPath = join5(root, ".claude", "skills", "lp-enhance", "SKILL.md");
1238
- const globalPath = join5(homedir(), ".claude", "skills", "lp-enhance", "SKILL.md");
1580
+ const projectPath = join6(root, ".claude", "skills", "lp-enhance", "SKILL.md");
1581
+ const globalPath = join6(homedir(), ".claude", "skills", "lp-enhance", "SKILL.md");
1239
1582
  const targetPath = await fileExists(projectPath) ? projectPath : await fileExists(globalPath) ? globalPath : null;
1240
1583
  if (!targetPath) return false;
1241
- await writeFile3(targetPath, generateEnhanceSkill());
1584
+ await writeFile4(targetPath, generateEnhanceSkill());
1242
1585
  log.success("Updated /lp-enhance skill to latest version");
1243
1586
  return true;
1244
1587
  }
@@ -1349,7 +1692,7 @@ function renderDoctorReport(results, options) {
1349
1692
  async function readJsonFile(path) {
1350
1693
  let raw;
1351
1694
  try {
1352
- raw = await readFile4(path, "utf-8");
1695
+ raw = await readFile5(path, "utf-8");
1353
1696
  } catch (err) {
1354
1697
  const code = err.code;
1355
1698
  if (code === "ENOENT") return {};
@@ -1364,20 +1707,20 @@ async function readJsonFile(path) {
1364
1707
  }
1365
1708
  }
1366
1709
  async function readSettingsJson(root) {
1367
- return readJsonFile(join6(root, ".claude", "settings.json"));
1710
+ return readJsonFile(join7(root, ".claude", "settings.json"));
1368
1711
  }
1369
1712
  async function writeSettingsJson(root, settings) {
1370
- const dir = join6(root, ".claude");
1371
- await mkdir3(dir, { recursive: true });
1372
- await writeFile4(join6(dir, "settings.json"), JSON.stringify(settings, null, 2) + "\n");
1713
+ const dir = join7(root, ".claude");
1714
+ await mkdir4(dir, { recursive: true });
1715
+ await writeFile5(join7(dir, "settings.json"), JSON.stringify(settings, null, 2) + "\n");
1373
1716
  }
1374
1717
  async function readSettingsLocalJson(root) {
1375
- return readJsonFile(join6(root, ".claude", "settings.local.json"));
1718
+ return readJsonFile(join7(root, ".claude", "settings.local.json"));
1376
1719
  }
1377
1720
  async function writeSettingsLocalJson(root, settings) {
1378
- const dir = join6(root, ".claude");
1379
- await mkdir3(dir, { recursive: true });
1380
- await writeFile4(join6(dir, "settings.local.json"), JSON.stringify(settings, null, 2) + "\n");
1721
+ const dir = join7(root, ".claude");
1722
+ await mkdir4(dir, { recursive: true });
1723
+ await writeFile5(join7(dir, "settings.local.json"), JSON.stringify(settings, null, 2) + "\n");
1381
1724
  }
1382
1725
 
1383
1726
  export {
@@ -1393,6 +1736,7 @@ export {
1393
1736
  generateClaudeignore,
1394
1737
  ENHANCE_SKILL_VERSION,
1395
1738
  generateEnhanceSkill,
1739
+ generateBacklogMd,
1396
1740
  readSettingsJson,
1397
1741
  writeSettingsJson,
1398
1742
  readSettingsLocalJson,
@@ -1401,10 +1745,12 @@ export {
1401
1745
  LP_STUB_OPEN,
1402
1746
  addOrUpdateHook,
1403
1747
  writeSprintHygieneScripts,
1748
+ writeWorkflowCheckScript,
1749
+ generateWorkflowRule,
1404
1750
  applyFixes,
1405
1751
  log,
1406
1752
  printBanner,
1407
1753
  printScoreCard,
1408
1754
  renderDoctorReport
1409
1755
  };
1410
- //# sourceMappingURL=chunk-RCYLZUU6.js.map
1756
+ //# sourceMappingURL=chunk-5KQ2JDZN.js.map