create-pathfinder 1.8.0 → 2.1.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 (45) hide show
  1. package/AGENTS.md +3 -1
  2. package/CLAUDE.md +20 -0
  3. package/README.md +383 -65
  4. package/context/ai-interaction.md +78 -39
  5. package/context/coding-standards.md +119 -60
  6. package/copy-list.json +1 -0
  7. package/package.json +2 -1
  8. package/roles/developer.md +37 -0
  9. package/roles/planner.md +38 -0
  10. package/roles/tester.md +40 -0
  11. package/skills/challenge-me/SKILL.md +11 -7
  12. package/skills/complete-feature/SKILL.md +16 -11
  13. package/skills/handoff/SKILL.md +24 -4
  14. package/skills/kickstart-pathfinder/SKILL.md +6 -1
  15. package/skills/learning-review/SKILL.md +9 -6
  16. package/skills/load-feature/SKILL.md +40 -10
  17. package/skills/prototype/SKILL.md +3 -1
  18. package/skills/quiz-me/SKILL.md +33 -7
  19. package/skills/review-feature/SKILL.md +19 -11
  20. package/skills/role/SKILL.md +33 -0
  21. package/skills/setup-tracker/SKILL.md +29 -61
  22. package/skills/start-feature/SKILL.md +19 -16
  23. package/skills/sync-tracker/SKILL.md +67 -106
  24. package/skills/teach-architecture/SKILL.md +6 -5
  25. package/skills/teach-feature/SKILL.md +11 -8
  26. package/skills/to-specs/SKILL.md +40 -28
  27. package/skills/whereami/SKILL.md +87 -0
  28. package/src/cli.mjs +67 -96
  29. package/src/install.mjs +5 -1
  30. package/src/kit.mjs +70 -1
  31. package/src/outcome.mjs +147 -0
  32. package/templates/CHANGELOG.template.md +2 -1
  33. package/templates/feature-spec.template.md +18 -80
  34. package/templates/history.template.md +12 -0
  35. package/templates/lesson.template.md +22 -51
  36. package/templates/project-overview.template.md +80 -145
  37. package/context/current-feature.md +0 -46
  38. package/context/features/example-feature-spec.md +0 -103
  39. package/context/history.md +0 -14
  40. package/context/learning/learner-profile.md +0 -35
  41. package/context/learning/lessons/.gitkeep +0 -0
  42. package/context/learning/progress.md +0 -28
  43. package/context/project-overview.md +0 -211
  44. package/templates/progress-entry.template.md +0 -23
  45. package/templates/tracker.template.md +0 -359
package/src/kit.mjs CHANGED
@@ -8,7 +8,7 @@
8
8
  */
9
9
 
10
10
  import { existsSync, readFileSync } from "node:fs";
11
- import { dirname, join, resolve } from "node:path";
11
+ import { dirname, join, relative, resolve, sep } from "node:path";
12
12
  import { fileURLToPath } from "node:url";
13
13
 
14
14
  const HERE = dirname(fileURLToPath(import.meta.url));
@@ -49,6 +49,75 @@ export function isExcluded(basename) {
49
49
  return EXCLUDED.has(basename) || basename.startsWith("._");
50
50
  }
51
51
 
52
+ /**
53
+ * Kit files that are deliberately not part of the kit, by kit-relative path.
54
+ *
55
+ * A different idea from EXCLUDED above, and kept separate for that reason.
56
+ * Those are OS and editor droppings that were never anybody's file. These are
57
+ * real, hand-written files that live inside a copy-list directory and must
58
+ * still never reach a destination project.
59
+ *
60
+ * All three are this repository's own working state, and every one of them
61
+ * would be actively wrong in somebody else's project.
62
+ *
63
+ * `context/tracker.md` is the original case. Work Tracking's off switch is the
64
+ * *absence* of that file in a destination project, so shipping this
65
+ * repository's own copy would hand every new project a configuration naming a
66
+ * tracker it does not own, pointing at a spec directory it does not have, with
67
+ * the off switch already defeated on first install.
68
+ *
69
+ * `context/current-feature.md` and `context/handoff.md` are transient session
70
+ * state, and they are here for the same reason one step further on: the kit
71
+ * stopped shipping a blank `current-feature.md` stencil, because `load-feature`
72
+ * writes the real one on first use and a placeholder is just a file a reader
73
+ * has to recognise as empty. Having stopped shipping the blank one, the thing
74
+ * to guard against is shipping a *filled-in* one — a destination project
75
+ * opening its first session to a note about whichever feature a Pathfinder
76
+ * maintainer had loaded on the day of the release.
77
+ *
78
+ * `context` is a *directory* entry in the copy list, so anything placed beneath
79
+ * it ships by default. Making the invariant enforced rather than intended is
80
+ * the same move `check_no_junk_tracked` made: an ignore rule is advisory, one
81
+ * `git add -f` defeats it, and `stage-kit.mjs` copies from the working tree
82
+ * without consulting it at all.
83
+ *
84
+ * Matched on the kit-relative path, never the basename — a project's own
85
+ * `tracker.md` somewhere else is not this file and must not be caught by it.
86
+ */
87
+ const NEVER_SHIPS = new Set([
88
+ "context/tracker.md",
89
+ "context/current-feature.md",
90
+ "context/handoff.md",
91
+ ]);
92
+
93
+ /**
94
+ * Is this kit-relative path one the kit must never hand over?
95
+ *
96
+ * @param {string} relativePath forward-slashed, relative to the kit root
97
+ */
98
+ export function neverShips(relativePath) {
99
+ return NEVER_SHIPS.has(relativePath);
100
+ }
101
+
102
+ /**
103
+ * A `cpSync` filter that drops never-ships files from a recursive copy.
104
+ *
105
+ * Lives here rather than inline in `stage-kit.mjs` so the staging path and its
106
+ * test run the same code. A test that rebuilds the predicate proves only that
107
+ * `cpSync` honours `filter`; it cannot catch the path arithmetic below going
108
+ * wrong, which is the part with anything to get wrong in it.
109
+ *
110
+ * `cpSync` hands the filter absolute paths and calls it for the copy root
111
+ * itself, so the root resolves to `""` and is kept — filtering a directory out
112
+ * would take its whole subtree with it.
113
+ *
114
+ * @param {string} rootDir absolute path the kit-relative paths are relative to
115
+ * @returns {(source: string) => boolean} true to copy, false to skip
116
+ */
117
+ export function neverShipsFilter(rootDir) {
118
+ return (source) => !neverShips(relative(rootDir, source).split(sep).join("/"));
119
+ }
120
+
52
121
  const PACKAGE_ROOT = resolve(HERE, "..");
53
122
 
54
123
  /**
@@ -0,0 +1,147 @@
1
+ /**
2
+ * What a run did, derived once.
3
+ *
4
+ * Two renderings print this install — `contractReport` owes byte-for-byte what
5
+ * 1.4.1 printed, `expressiveReport` owes a person a legible hierarchy — and
6
+ * they had each grown their own copy of the same four derivations: the
7
+ * mode-dependent written count, the skipped filter, the failure merge, and a
8
+ * per-harness adapter tally that appeared three times character-for-character.
9
+ * Four facts, ten spellings, and no mechanism keeping them in agreement. This
10
+ * module is the one spelling. The renderings stay two renderings; they just
11
+ * stop each deciding what the numbers are.
12
+ *
13
+ * Pure by construction: no filesystem, no `process`, no writing. Everything
14
+ * here is a function of the plans and results it is handed, which is what lets
15
+ * a summary be tested without building a temporary repository or a fake
16
+ * terminal.
17
+ *
18
+ * Two counters elsewhere are deliberately *not* folded in, and a later change
19
+ * that "finishes the job" will break them:
20
+ *
21
+ * - `countWritten` in `cli.mjs` runs mid-run, before adapters are applied, and
22
+ * reports a different number — `written + overwritten`, labelled "copied" —
23
+ * than the summary's `written`. It cannot read a result that does not exist
24
+ * yet.
25
+ * - The streaming per-harness counts in `generateAdapters` accumulate as units
26
+ * resolve, so milestones can print while the work is happening, and they
27
+ * count conflicts, orphans, and up-to-date adapters differently from the
28
+ * rows below. A summary computed at the end cannot drive a progress bar.
29
+ */
30
+
31
+ /**
32
+ * Every derived fact both renderings need, and nothing either of them can
33
+ * compute for itself.
34
+ *
35
+ * @param {object} args
36
+ * @param {{relativePath: string, status: "write"|"skip"|"overwrite"}[]} args.plan
37
+ * the kit copy plan, in `planInstall`'s sort
38
+ * @param {{written: number, skipped: number, overwritten: number,
39
+ * errors: {relativePath: string, message: string}[]}} args.result
40
+ * @param {{plan: object[], result: object, blocked: boolean}} args.adapters
41
+ * @param {{label: string}[]} args.harnesses the selected harnesses, registry order
42
+ * @param {{dryRun?: boolean}} args.options
43
+ * @returns {Readonly<object>} frozen; rows and lists frozen with it
44
+ */
45
+ export function summarize({ plan, result, adapters, harnesses, options }) {
46
+ // A dry run has no `result.written` to report, because nothing was written.
47
+ // The plan is counted instead, which is the same number the run would have
48
+ // produced had it been allowed to write.
49
+ const written = options.dryRun
50
+ ? plan.filter((item) => item.status === "write").length
51
+ : result.written;
52
+
53
+ const skipped = plan
54
+ .filter((item) => item.status === "skip")
55
+ .map((item) => item.relativePath);
56
+
57
+ // Copy errors before adapter errors, because that is the order they happened
58
+ // in and the order the failure list has always printed.
59
+ const failures = Object.freeze([...result.errors, ...adapters.result.errors]);
60
+
61
+ return Object.freeze({
62
+ written,
63
+ overwritten: result.overwritten,
64
+ skipped: Object.freeze(skipped),
65
+ // Nothing to write and every file already there. Not the same as `written
66
+ // === 0`, which a partly failed copy also satisfies.
67
+ alreadyInstalled: written === 0 && skipped.length === plan.length,
68
+ failures,
69
+ blocked: adapters.blocked,
70
+ // Summed from the result, never from `harnessRows`. The rows exclude paths
71
+ // that errored and this does not, so the two disagree exactly when a write
72
+ // fails — and this is the number the closing headline speaks for.
73
+ built: adapters.result.generated + adapters.result.replaced,
74
+ attention: attentionCount(adapters),
75
+ harnessRows: harnessRows({ adapters, harnesses }),
76
+ });
77
+ }
78
+
79
+ /**
80
+ * What actually wants a human: a contested path, or an adapter pointing at a
81
+ * skill that is gone.
82
+ *
83
+ * Skipped files are deliberately not counted. A re-run over an existing install
84
+ * skips every file by design, and calling thirty-six routine skips "things to
85
+ * look at" would turn the one number that should mean something into noise
86
+ * nobody reads twice.
87
+ *
88
+ * Counted across the whole adapter plan rather than across `harnessRows`,
89
+ * errored paths included, because a path that could not be written is still a
90
+ * path somebody has to go and look at.
91
+ */
92
+ function attentionCount(adapters) {
93
+ if (adapters.blocked) return 0;
94
+ return adapters.plan.filter(
95
+ (item) => item.action === "conflict" || item.action === "orphan",
96
+ ).length;
97
+ }
98
+
99
+ /**
100
+ * One row per selected harness, in the order the harnesses were given.
101
+ *
102
+ * The three-way distinction the report depends on is carried by the rows
103
+ * themselves, and all three collapse to a zero if it is lost:
104
+ *
105
+ * - no harness chosen — no rows, `blocked: false`
106
+ * - the kit copy failed — no rows, `blocked: true`
107
+ * - a harness that produced nothing — a row of zeroes
108
+ *
109
+ * The blocked case returns no rows explicitly rather than falling out of an
110
+ * empty plan, so that a harness which was chosen and never reached is never
111
+ * described as having generated zero adapters.
112
+ *
113
+ * Paths that failed to write are excluded from every count and list here: an
114
+ * adapter that could not be written was not generated, is not up to date, and
115
+ * is not a conflict the user can resolve by re-running with `--force`. They are
116
+ * reported once, as failures.
117
+ */
118
+ function harnessRows({ adapters, harnesses }) {
119
+ if (harnesses.length === 0 || adapters.blocked) return Object.freeze([]);
120
+
121
+ const failed = new Set(adapters.result.errors.map((error) => error.relativePath));
122
+
123
+ return Object.freeze(
124
+ harnesses.map((harness) => {
125
+ // Identity, not label: the harness object on a plan item is the registry
126
+ // entry itself, and two entries could plausibly share a label one day.
127
+ const mine = adapters.plan.filter(
128
+ (item) => item.harness === harness && !failed.has(item.relativePath),
129
+ );
130
+ const count = (action) => mine.filter((item) => item.action === action).length;
131
+ const paths = (action) =>
132
+ Object.freeze(
133
+ mine.filter((item) => item.action === action).map((item) => item.relativePath),
134
+ );
135
+
136
+ return Object.freeze({
137
+ harness,
138
+ generated: count("write"),
139
+ replaced: count("replace"),
140
+ unchanged: count("up-to-date"),
141
+ // `planAdapters` order, which is the order they will be printed in.
142
+ conflicts: paths("conflict"),
143
+ orphans: paths("orphan"),
144
+ });
145
+ }),
146
+ );
147
+ }
@@ -1,6 +1,7 @@
1
1
  # Changelog
2
2
 
3
- Use this file only when the destination project adopts a changelog.
3
+ Use only when the project adopts a changelog.
4
+ Record user-facing changes, not implementation history.
4
5
 
5
6
  ## [Unreleased]
6
7
 
@@ -2,104 +2,42 @@
2
2
 
3
3
  ## Status
4
4
 
5
- Not Started
6
-
7
- ## Overview
8
-
9
- Describe the smallest coherent outcome, where it fits, and why it matters.
10
-
11
- ## Problem
12
-
13
- - What is missing, risky, confusing, inaccessible, unreliable, or inefficient?
14
- - Who or what is affected?
15
- - Why does this matter now?
5
+ Proposed
16
6
 
17
7
  ## Goal
18
8
 
19
- State one user-visible or system-verifiable outcome.
20
-
21
- ## Dependencies
9
+ [One clear user-visible or system-verifiable outcome.]
22
10
 
23
- - Earlier feature, system, decision, prototype, or `None`.
11
+ ## Context
24
12
 
25
- ## Tags
13
+ Include only what materially helps someone implement this Feature.
26
14
 
27
- - Optional. `namespace:value`, e.g. `area:cli`, `type:infra`, `agent:suitable`.
28
- - Omit this section entirely, or write `None`, when the project does not use tags. Most do not.
29
- - Only meaningful when the project has configured work tracking; nothing else reads them.
15
+ - Read: `[specific files, sections, or systems]`
16
+ - Relevant area: `[path or component]`
17
+ - Avoid: `[unrelated area, if useful]`
30
18
 
31
19
  ## Requirements
32
20
 
33
- - Requirement 1
34
- - Requirement 2
35
- - Requirement 3
36
- - Include relevant failure, permission, loading, empty, retry, responsive, accessibility, operational, or compatibility states only when applicable.
21
+ - `[required behavior]`
22
+ - `[important constraint]`
23
+ - `[relevant edge or failure behavior, when applicable]`
37
24
 
38
25
  ## Out of Scope
39
26
 
40
- - Explicit exclusion
41
- - Later feature, if known
42
- - Unrelated refactors, dependencies, or polish
43
-
44
- ## Experience or Operational Notes — When Applicable
45
-
46
- - User interaction, system behavior, responsive behavior, accessibility, observability, performance, security, or operational expectations.
47
-
48
- ## Technical Notes
49
-
50
- Likely areas:
51
-
52
- - `[specific path or bounded area]`
53
-
54
- Implementation constraints:
55
-
56
- - Follow project context and approved prototype direction.
57
- - Do not silently resolve open architecture decisions.
58
- - Keep prototype and production code boundaries explicit.
59
-
60
- ## Context Boundary
61
-
62
- Read:
63
-
64
- - `[specific context and code]`
65
-
66
- Avoid loading:
67
-
68
- - `[unrelated systems, old specs, generated output]`
69
-
70
- Split this feature further if its required context is not focused enough for reliable implementation and verification.
27
+ - `[explicit exclusion]`
71
28
 
72
29
  ## Delivery Chunks
73
30
 
74
- 1. `[stable, verifiable increment]`
75
- 2. `[stable, verifiable increment]`
76
- 3. `[optional stable increment]`
31
+ 1. `[small, stable, verifiable increment]`
32
+ 2. `[next increment, if needed]`
77
33
 
78
- Each chunk should leave the project stable.
34
+ Use one chunk when the Feature is already small.
79
35
 
80
36
  ## Acceptance Criteria
81
37
 
82
- - The defined outcome works end to end within scope.
83
- - Relevant failure and edge behavior is handled.
84
- - Applicable quality requirements are met.
85
- - Required automated/manual checks pass.
86
- - The work can be reviewed independently.
87
-
88
- ## Verification
89
-
90
- - Manual or operational checks:
91
- - Automated checks:
92
- - Edge cases:
93
- - Quality checks:
94
-
95
- ## Learning Targets
96
-
97
- - Concepts worth explaining after completion:
98
- - Diagram or demonstration opportunity:
99
- - Quiz ideas:
38
+ - `[observable result proving the Feature works]`
39
+ - `[important verification result]`
100
40
 
101
- ## Suggested Delivery Metadata
41
+ ## Notes / Decisions
102
42
 
103
- - Git action: `[follow project workflow]`
104
- - Suggested branch, when applicable: `[project naming convention]`
105
- - Suggested commit, when applicable: `[project convention]`
43
+ - `[Feature-specific dependency, approved decision, constraint, or None]`
@@ -0,0 +1,12 @@
1
+ # Project History
2
+
3
+ Compact record of completed work.
4
+
5
+ ## Completed
6
+
7
+ ### [YYYY-MM-DD] — [Feature or milestone]
8
+
9
+ - Outcome: `[what changed for the project/user]`
10
+ - Verification: `[brief result or evidence pointer]`
11
+ - Commit/PR: `[sha, PR, or release reference]`
12
+ - Follow-up: `[only if something remains, otherwise none]`
@@ -1,71 +1,42 @@
1
1
  # Lesson — [Feature Name]
2
2
 
3
3
  - Date:
4
- - Branch:
5
- - Feature spec:
6
- - Commit or diff range:
7
- - Difficulty:
8
- - Estimated review time:
4
+ - Feature:
5
+ - Commit/diff:
9
6
 
10
- ## 1. Feature in One Minute
7
+ ## What Changed
11
8
 
12
- What changed, why it exists, and the user or system value it creates.
9
+ Explain what was implemented and why it matters.
13
10
 
14
- ## 2. Mental Model
11
+ ## Mental Model
15
12
 
16
- Explain the feature through one clear model. Include a small Mermaid diagram when useful.
13
+ Explain the simplest useful way to understand the Feature.
17
14
 
18
- ## 3. Execution and Data Flow
15
+ Include a small diagram only when it helps.
19
16
 
20
- Trace one representative interaction from input to visible result.
17
+ ## How It Works
21
18
 
22
- ## 4. Important Files
19
+ Trace one representative flow through the implementation.
23
20
 
24
- | File | Responsibility | Why it matters |
25
- | --- | --- | --- |
21
+ ## Important Files
26
22
 
27
- ## 5. Key Decisions and Tradeoffs
23
+ | File | Responsibility |
24
+ | --- | --- |
28
25
 
29
- For each meaningful decision:
26
+ Include only files that materially help understanding.
30
27
 
31
- - Decision
32
- - Evidence in the repository
33
- - Benefit
34
- - Cost
35
- - Credible alternative
36
- - When the alternative would be better
28
+ ## Key Decisions
37
29
 
38
- ## 6. Concepts Worth Retaining
30
+ - `[important decision and why it was made]`
39
31
 
40
- Limit this to the most transferable concepts.
32
+ Include only decisions worth remembering.
41
33
 
42
- ## 7. Quality Lens
34
+ ## Concepts to Retain
43
35
 
44
- ### Testing
36
+ - `[transferable concept]`
45
37
 
46
- ### Accessibility
38
+ ## Check Your Understanding
47
39
 
48
- ### Performance
49
-
50
- ### Maintainability
51
-
52
- ### Error and edge states
53
-
54
- ## 8. Production-Scale Gap
55
-
56
- What would need to change for higher traffic, larger datasets, multiple teams, stricter security, or real-time collaboration?
57
-
58
- ## 9. Interview Preparation
59
-
60
- - Likely question:
61
- - Strong answer outline:
62
- - Follow-up question:
63
- - Vocabulary to use carefully:
64
-
65
- ## 10. Check Your Understanding
66
-
67
- Three short retrieval questions. Do not include answers here.
68
-
69
- ## 11. Suggested Next Action
70
-
71
- Choose one: quiz, challenge, architecture lesson, spaced review, or no further work.
40
+ 1. `[question]`
41
+ 2. `[question]`
42
+ 3. `[question]`