create-pathfinder 2.0.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +1 -4
- package/context/ai-interaction.md +4 -4
- package/package.json +1 -1
- package/roles/developer.md +1 -2
- package/roles/tester.md +1 -1
- package/skills/debug-issue/SKILL.md +4 -4
- package/skills/feature/SKILL.md +56 -0
- package/skills/feature/actions/complete.md +22 -0
- package/skills/feature/actions/load.md +45 -0
- package/skills/{review-feature/SKILL.md → feature/actions/review.md} +5 -6
- package/skills/feature/actions/start.md +33 -0
- package/skills/reverse-engineer/SKILL.md +1 -1
- package/skills/role/SKILL.md +1 -0
- package/src/cli.mjs +119 -109
- package/src/kit.mjs +1 -1
- package/src/outcome.mjs +147 -0
- package/skills/complete-feature/SKILL.md +0 -23
- package/skills/load-feature/SKILL.md +0 -28
- package/skills/start-feature/SKILL.md +0 -29
package/CLAUDE.md
CHANGED
|
@@ -79,11 +79,8 @@ An adapter carries the canonical skill's frontmatter and a pointer to it, and no
|
|
|
79
79
|
- `reverse-engineer` — analyze an external reference and produce an evidence-based reconstruction blueprint
|
|
80
80
|
- `prototype` — create and iterate the cheapest useful validation artifact
|
|
81
81
|
- `to-specs` — generate context-sized feature specs
|
|
82
|
-
- `
|
|
83
|
-
- `start-feature` — implement scoped delivery chunks
|
|
82
|
+
- `feature` — run one action of the Feature delivery loop: `load`, `start`, `review`, `complete`
|
|
84
83
|
- `debug-issue` — diagnose an observed failure to its root cause, apply the smallest justified fix, and verify it
|
|
85
|
-
- `review-feature` — review against requirements, regressions, and standards
|
|
86
|
-
- `complete-feature` — verify and close a feature cleanly
|
|
87
84
|
- `learn-feature` — create an interactive lesson for a completed feature
|
|
88
85
|
- `learn-codebase` — create a modular learning portal for the repository
|
|
89
86
|
- `teach-feature` — teach the verified current feature from its spec, diff, tests, and implementation
|
|
@@ -38,10 +38,10 @@ Inspect current Git state before acting. If the workflow is unclear or
|
|
|
38
38
|
|
|
39
39
|
Use the workflow skills instead of recreating their procedures in chat:
|
|
40
40
|
|
|
41
|
-
1.
|
|
42
|
-
2.
|
|
43
|
-
3.
|
|
44
|
-
4.
|
|
41
|
+
1. `/feature load` — load the active work and relevant context.
|
|
42
|
+
2. `/feature start` — implement the current delivery chunk.
|
|
43
|
+
3. `/feature review` — verify the work and report findings.
|
|
44
|
+
4. `/feature complete` — complete accepted work and durable records.
|
|
45
45
|
5. `learn-feature` — optionally teach what was implemented.
|
|
46
46
|
|
|
47
47
|
Roles are optional. The workflow must work without activating one.
|
package/package.json
CHANGED
package/roles/developer.md
CHANGED
|
@@ -17,8 +17,7 @@ Do not load unrelated history, roadmap, Features, or repository areas by default
|
|
|
17
17
|
|
|
18
18
|
## Use
|
|
19
19
|
|
|
20
|
-
- `
|
|
21
|
-
- `start-feature` to implement it.
|
|
20
|
+
- `feature` — its load action to load the work, its start action to implement it.
|
|
22
21
|
- Use the project's existing build, test, and verification commands as needed.
|
|
23
22
|
|
|
24
23
|
## Rules
|
package/roles/tester.md
CHANGED
|
@@ -20,7 +20,7 @@ Do not rely on the developer's summary as proof.
|
|
|
20
20
|
|
|
21
21
|
## Use
|
|
22
22
|
|
|
23
|
-
- `
|
|
23
|
+
- `feature` — its review action, to verify implemented work.
|
|
24
24
|
- Use the project's existing test commands and relevant testing tools.
|
|
25
25
|
|
|
26
26
|
## Rules
|
|
@@ -29,9 +29,9 @@ Use Debug Issue when there is a concrete unexpected behavior such as:
|
|
|
29
29
|
|
|
30
30
|
Do not use it merely because implementation work is difficult.
|
|
31
31
|
|
|
32
|
-
If the task is planned feature construction, use
|
|
32
|
+
If the task is planned feature construction, use `/feature start`.
|
|
33
33
|
|
|
34
|
-
If the goal is reviewing completed implementation for possible defects, use
|
|
34
|
+
If the goal is reviewing completed implementation for possible defects, use `/feature review`.
|
|
35
35
|
|
|
36
36
|
If the real question is broad understanding of the repository, use `learn-codebase`.
|
|
37
37
|
|
|
@@ -202,8 +202,8 @@ It does not:
|
|
|
202
202
|
|
|
203
203
|
* implement unrelated feature scope
|
|
204
204
|
* perform a general repository review
|
|
205
|
-
* replace
|
|
206
|
-
* replace
|
|
205
|
+
* replace `/feature review`
|
|
206
|
+
* replace `/feature complete`
|
|
207
207
|
* silently change architecture or dependencies
|
|
208
208
|
* turn debugging into opportunistic refactoring
|
|
209
209
|
* hide uncertainty behind a successful-looking workaround
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: feature
|
|
3
|
+
description: Run one action of the Feature delivery loop — load, start, review, or complete.
|
|
4
|
+
argument-hint: load|start|review|complete
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Feature
|
|
8
|
+
|
|
9
|
+
The Feature delivery loop, as one skill. The human names the action:
|
|
10
|
+
|
|
11
|
+
`/feature load`
|
|
12
|
+
`/feature start`
|
|
13
|
+
`/feature review`
|
|
14
|
+
`/feature complete`
|
|
15
|
+
|
|
16
|
+
## Process
|
|
17
|
+
|
|
18
|
+
1. Take the action from the invocation.
|
|
19
|
+
If none was given, list the four actions below and stop.
|
|
20
|
+
If it is not one of the four, say so, list them, and stop.
|
|
21
|
+
2. Read only `skills/feature/actions/<action>.md` and follow it exactly.
|
|
22
|
+
|
|
23
|
+
## Actions
|
|
24
|
+
|
|
25
|
+
- `load` — prepare one Feature for execution. Reads its spec and the minimum
|
|
26
|
+
context around it, checks for blockers, records the approval in the spec, and
|
|
27
|
+
writes the session's workspace state.
|
|
28
|
+
- `start` — implement the active delivery chunk in small, stable increments,
|
|
29
|
+
restating the pre-implementation summary before editing anything.
|
|
30
|
+
- `review` — verify implemented work against its Feature and report findings.
|
|
31
|
+
It changes no implementation and accepts nothing.
|
|
32
|
+
- `complete` — complete work the human has accepted, through the project's
|
|
33
|
+
delivery workflow and its durable records.
|
|
34
|
+
|
|
35
|
+
## Lifecycle
|
|
36
|
+
|
|
37
|
+
The Feature spec's `## Status` holds the durable lifecycle state named in
|
|
38
|
+
`context/ai-interaction.md`:
|
|
39
|
+
|
|
40
|
+
`Proposed` → `Ready` → `In Progress` → `Complete`
|
|
41
|
+
|
|
42
|
+
`Cancelled` and `Superseded` are terminal alternatives.
|
|
43
|
+
|
|
44
|
+
- `load` writes `Proposed` → `Ready`.
|
|
45
|
+
- `start` writes `Ready` → `In Progress`.
|
|
46
|
+
- `complete` writes `Complete`.
|
|
47
|
+
- `review` writes no status. Review is workflow activity, not lifecycle state.
|
|
48
|
+
|
|
49
|
+
`context/current-feature.md` is transient workspace state and records no status.
|
|
50
|
+
|
|
51
|
+
## Rules
|
|
52
|
+
|
|
53
|
+
- Run the one action the human named. Do not continue into the next one.
|
|
54
|
+
- Read only that action's file. The other three are not context for this work.
|
|
55
|
+
- Human authority is unchanged: approval, acceptance, merge, and release are
|
|
56
|
+
the human's, whichever action is running.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Feature: Complete
|
|
2
|
+
|
|
3
|
+
Invoking this action is the human's acceptance of the Feature. Do not ask for
|
|
4
|
+
acceptance again.
|
|
5
|
+
|
|
6
|
+
Acceptance of the Feature is not approval of the delivery steps below. Each one
|
|
7
|
+
that the project's documented workflow gates is still asked for.
|
|
8
|
+
|
|
9
|
+
1. Run the project's required final checks.
|
|
10
|
+
2. Follow the documented Git, merge, version, release, and deployment workflow.
|
|
11
|
+
Ask for approval where it requires it. If that workflow is undocumented or
|
|
12
|
+
`TBD`, stop and ask rather than choosing one.
|
|
13
|
+
3. Mark the Feature `Complete` in its spec's `## Status`.
|
|
14
|
+
4. Add a compact entry to `context/history.md`.
|
|
15
|
+
Create it from `templates/history.template.md` if it does not exist.
|
|
16
|
+
5. Clear or update `context/current-feature.md`. Do nothing if it does not exist.
|
|
17
|
+
6. If work tracking is configured, sync the completed state.
|
|
18
|
+
7. Report the completed outcome and any remaining follow-up.
|
|
19
|
+
|
|
20
|
+
Do not re-review accepted work unless final verification exposes a new problem.
|
|
21
|
+
|
|
22
|
+
Do not claim completion if required checks or delivery steps failed.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Feature: Load
|
|
2
|
+
|
|
3
|
+
Prepare one Feature for execution.
|
|
4
|
+
|
|
5
|
+
1. Select the Feature the human named. Being invoked on it is the approval to
|
|
6
|
+
prepare it for execution, so its spec does not already have to say `Ready`.
|
|
7
|
+
Its spec is `NN-feature-name.md` in the spec source, and `NN` is its Feature
|
|
8
|
+
number.
|
|
9
|
+
2. Read the Feature spec.
|
|
10
|
+
3. Read only the files or context needed for the current work.
|
|
11
|
+
4. Inspect the current Git state.
|
|
12
|
+
5. Stop if a required human decision or explicit dependency blocks the work.
|
|
13
|
+
6. Record the approval in the Feature spec's `## Status`, which holds the
|
|
14
|
+
durable lifecycle state:
|
|
15
|
+
- `Proposed` becomes `Ready`. That is the only value this action writes.
|
|
16
|
+
- `Ready` or `In Progress` is left exactly as it is. Reloading a Feature
|
|
17
|
+
mid-work is normal and must not rewrite its state.
|
|
18
|
+
- `Complete`, `Cancelled`, or `Superseded` blocks the load. Report it and
|
|
19
|
+
stop. Reopening terminal work is the human's decision.
|
|
20
|
+
|
|
21
|
+
Do this only once steps 1-5 found no blocker, and before the next step, so a
|
|
22
|
+
blocked load never leaves a promoted spec behind.
|
|
23
|
+
7. Create or update `context/current-feature.md` — it does not ship, so the
|
|
24
|
+
first load writes it — with:
|
|
25
|
+
- Feature number, name, and spec path
|
|
26
|
+
- active delivery chunk
|
|
27
|
+
- Git state
|
|
28
|
+
- blocker, if any
|
|
29
|
+
- next action
|
|
30
|
+
|
|
31
|
+
Do not record the lifecycle status here. This file is transient workspace
|
|
32
|
+
state belonging to one session on one machine; the spec carries the durable
|
|
33
|
+
status.
|
|
34
|
+
8. If `context/tracker.md` exists, name the tracked item for this Feature —
|
|
35
|
+
its key is that Feature number. Do nothing here if it does not.
|
|
36
|
+
9. Present a short readiness summary.
|
|
37
|
+
|
|
38
|
+
Do not implement the Feature. That is `/feature start`.
|
|
39
|
+
|
|
40
|
+
Do not scan unrelated repository areas, load history or roadmap by default, or
|
|
41
|
+
silently resolve `TBD` decisions.
|
|
42
|
+
|
|
43
|
+
Do not rewrite the Feature's substance — its Goal, Context, Requirements, Out of
|
|
44
|
+
Scope, Delivery Chunks, or Acceptance Criteria. `## Status` is the one field this
|
|
45
|
+
action maintains.
|
|
@@ -1,9 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
name: review-feature
|
|
3
|
-
description: Verify implemented work against its Feature and report findings.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Review Feature
|
|
1
|
+
# Feature: Review
|
|
7
2
|
|
|
8
3
|
Review the actual diff and behavior, not only the developer's summary.
|
|
9
4
|
|
|
@@ -31,3 +26,7 @@ Report:
|
|
|
31
26
|
Do not modify the implementation unless the human explicitly asks.
|
|
32
27
|
|
|
33
28
|
Do not invent findings or treat passing tests as automatic acceptance.
|
|
29
|
+
|
|
30
|
+
Do not write the Feature's `## Status`. Review is workflow activity, not
|
|
31
|
+
lifecycle state, and a reviewed Feature stays `In Progress` until it is
|
|
32
|
+
completed.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Feature: Start
|
|
2
|
+
|
|
3
|
+
Implement the active delivery chunk.
|
|
4
|
+
|
|
5
|
+
1. Read only the context needed for the current work.
|
|
6
|
+
2. Restate the pre-implementation summary the project's agent guide requires,
|
|
7
|
+
and wait for it to be answered where it names a human decision. Do not edit
|
|
8
|
+
any file before this step is done.
|
|
9
|
+
3. Record the Feature spec's `## Status` as `In Progress`, before the first file
|
|
10
|
+
edit of the first chunk:
|
|
11
|
+
- `Ready` becomes `In Progress`. That is the only value this action writes.
|
|
12
|
+
- `In Progress` is left exactly as it is. Continuing a Feature across
|
|
13
|
+
sessions and chunks is normal and must not rewrite its state.
|
|
14
|
+
- Any other status stops the session. Report it. A Feature that was never
|
|
15
|
+
loaded is not approved for execution, and terminal work is the human's to
|
|
16
|
+
reopen.
|
|
17
|
+
4. Follow the Feature and existing project conventions.
|
|
18
|
+
5. Make the smallest complete change.
|
|
19
|
+
6. Verify the behavior you changed.
|
|
20
|
+
7. Keep the repository stable.
|
|
21
|
+
8. Create or update `context/current-feature.md` with the current state and next
|
|
22
|
+
action. Do not record the lifecycle status there; the spec carries it.
|
|
23
|
+
|
|
24
|
+
## Rules
|
|
25
|
+
|
|
26
|
+
- Stay inside the approved scope.
|
|
27
|
+
- Stop if implementation requires a human decision or material scope change.
|
|
28
|
+
- Do not silently add dependencies or adopt prototype code.
|
|
29
|
+
- Follow the project's approval and Git rules.
|
|
30
|
+
- Do not decide that your own work is accepted.
|
|
31
|
+
|
|
32
|
+
When the chunk is done, report what changed, what was verified, and anything
|
|
33
|
+
unresolved.
|
|
@@ -173,7 +173,7 @@ Use one of these:
|
|
|
173
173
|
* `debate-me` — when major product or technical decisions remain
|
|
174
174
|
* `prototype` — when the experience or technical assumption needs validation
|
|
175
175
|
* `to-specs` — when the direction is approved and ready to become feature files
|
|
176
|
-
*
|
|
176
|
+
* `/feature load` — when the analysis applies to one already-planned feature
|
|
177
177
|
* `learn-codebase` — when the user wants to understand their own repository instead
|
|
178
178
|
* no handoff — when the user only requested analysis
|
|
179
179
|
|
package/skills/role/SKILL.md
CHANGED
package/src/cli.mjs
CHANGED
|
@@ -16,6 +16,7 @@ import { detectEditors, openInEditor } from "./editor.mjs";
|
|
|
16
16
|
import { kickstartPrompt, kickstartPromptLines } from "./kickstart-prompt.mjs";
|
|
17
17
|
import { createTheme } from "./theme.mjs";
|
|
18
18
|
import { createProgress } from "./progress.mjs";
|
|
19
|
+
import { summarize } from "./outcome.mjs";
|
|
19
20
|
import {
|
|
20
21
|
HARNESSES,
|
|
21
22
|
HARNESS_IDS,
|
|
@@ -48,6 +49,8 @@ Options:
|
|
|
48
49
|
It does not authorize \`git init\` or configure any tool;
|
|
49
50
|
pass --git-init and --agents for those.
|
|
50
51
|
-h, --help Show this message.
|
|
52
|
+
-v, --version Print the version and exit, whatever else you passed.
|
|
53
|
+
Nothing else is printed.
|
|
51
54
|
|
|
52
55
|
Adapters are generated files Pathfinder owns and regenerates without --force.
|
|
53
56
|
A file it did not generate is never replaced, at any path, without --force.
|
|
@@ -82,6 +85,22 @@ export async function run(
|
|
|
82
85
|
) {
|
|
83
86
|
const options = parseArguments(argv);
|
|
84
87
|
|
|
88
|
+
// Answered before the error branch, and before every other flag: `--version`
|
|
89
|
+
// is a question about this package, not about this run. It holds outside a Git
|
|
90
|
+
// repository, alongside a misspelled flag, and when stdout is redirected to a
|
|
91
|
+
// file or a pipe, because the one thing a caller asking for a version number
|
|
92
|
+
// can never use is a paragraph explaining why the version could not be
|
|
93
|
+
// printed. A reader that closes the pipe before this write lands still raises
|
|
94
|
+
// EPIPE, as it does for every other write here; that is not something this
|
|
95
|
+
// early return changes.
|
|
96
|
+
//
|
|
97
|
+
// One line, `VERSION` alone. No `v` prefix, no identity block, no findings:
|
|
98
|
+
// this output is read by scripts, and the plain form is the whole contract.
|
|
99
|
+
if (options.version) {
|
|
100
|
+
out(`${VERSION}\n`);
|
|
101
|
+
return 0;
|
|
102
|
+
}
|
|
103
|
+
|
|
85
104
|
if (options.error) {
|
|
86
105
|
err(`create-pathfinder: ${options.error}\n\n${USAGE}`);
|
|
87
106
|
return 2;
|
|
@@ -270,7 +289,13 @@ export async function run(
|
|
|
270
289
|
progress.finish();
|
|
271
290
|
if (!options.dryRun && theme.tier !== "contract") out("\n");
|
|
272
291
|
|
|
273
|
-
|
|
292
|
+
// Derived here, once, and handed down. Below this line nothing re-reads a
|
|
293
|
+
// plan or a result: the two renderings disagree about everything except the
|
|
294
|
+
// facts, and this is what makes "except the facts" true rather than a hope
|
|
295
|
+
// about two functions being edited together.
|
|
296
|
+
const outcome = summarize({ plan, result, adapters, harnesses, options });
|
|
297
|
+
|
|
298
|
+
report({ outcome, harnesses, customTools, cwd, gitRoot, options, out, err, theme });
|
|
274
299
|
|
|
275
300
|
// After the report, because the first offer is about the prompt the report
|
|
276
301
|
// just printed — and because a question above the summary would make the user
|
|
@@ -286,7 +311,7 @@ export async function run(
|
|
|
286
311
|
//
|
|
287
312
|
// Not printed when anything failed. A sign-off over an error is a tool that
|
|
288
313
|
// did not read its own output.
|
|
289
|
-
const failed =
|
|
314
|
+
const failed = outcome.failures.length > 0;
|
|
290
315
|
if (theme.tier !== "contract" && !failed) {
|
|
291
316
|
out(`\n ${theme.dim(SIGN_OFF)}\n`);
|
|
292
317
|
}
|
|
@@ -497,9 +522,9 @@ function generateAdapters({ plan, harnesses, options, result, onProgress, onHarn
|
|
|
497
522
|
* person reading has just watched a bar fill. A re-run that wrote nothing says
|
|
498
523
|
* so plainly instead of inventing an achievement.
|
|
499
524
|
*/
|
|
500
|
-
function endingHeadline({ theme,
|
|
525
|
+
function endingHeadline({ theme, outcome, options }) {
|
|
501
526
|
const mark = theme.glyph;
|
|
502
|
-
const built
|
|
527
|
+
const { written, built, attention } = outcome;
|
|
503
528
|
|
|
504
529
|
// Something wants a human. Still ready — it is — but this is not the moment
|
|
505
530
|
// for confetti over somebody's conflicted file.
|
|
@@ -523,9 +548,9 @@ function endingHeadline({ theme, written, adapters, attention, options }) {
|
|
|
523
548
|
* so plainly instead of inventing an achievement out of the harnesses it did
|
|
524
549
|
* not have to configure.
|
|
525
550
|
*/
|
|
526
|
-
function endingDetail({
|
|
551
|
+
function endingDetail({ outcome, harnesses, options }) {
|
|
527
552
|
const parts = [];
|
|
528
|
-
const built
|
|
553
|
+
const { written, built, attention } = outcome;
|
|
529
554
|
|
|
530
555
|
if (written > 0) parts.push(`${written} file${plural(written)}`);
|
|
531
556
|
if (built > 0) parts.push(`${built} adapter${plural(built)}`);
|
|
@@ -563,21 +588,43 @@ function countWritten(result, plan, options) {
|
|
|
563
588
|
return result.written + result.overwritten;
|
|
564
589
|
}
|
|
565
590
|
|
|
591
|
+
/**
|
|
592
|
+
* Every option at its unasked-for default. `parseArguments` starts from a copy
|
|
593
|
+
* of this and `--version` returns one, so the two agree by construction.
|
|
594
|
+
*/
|
|
595
|
+
const NO_OPTIONS = {
|
|
596
|
+
dryRun: false,
|
|
597
|
+
force: false,
|
|
598
|
+
help: false,
|
|
599
|
+
version: false,
|
|
600
|
+
gitInit: false,
|
|
601
|
+
noGitInit: false,
|
|
602
|
+
noClipboard: false,
|
|
603
|
+
noOpen: false,
|
|
604
|
+
yes: false,
|
|
605
|
+
// null means "not said", which is not the same as "none". Only the first
|
|
606
|
+
// suppresses the question.
|
|
607
|
+
agents: null,
|
|
608
|
+
error: null,
|
|
609
|
+
};
|
|
610
|
+
|
|
611
|
+
/**
|
|
612
|
+
* Which options this run asks for.
|
|
613
|
+
*
|
|
614
|
+
* `-v` and `--version` are recognized here, before any other token in `argv`
|
|
615
|
+
* is interpreted, because they ask what this package is rather than what this
|
|
616
|
+
* run should do. A misspelled flag or an invalid `--agents` value must not
|
|
617
|
+
* swallow the answer, and the answer must not depend on position: a wrapper
|
|
618
|
+
* that appends `--version` to arguments it was handed still gets a version
|
|
619
|
+
* number. That is why this is a scan and not another `case` below — the
|
|
620
|
+
* refusals in the loop return early, and one of them would otherwise win.
|
|
621
|
+
*/
|
|
566
622
|
function parseArguments(argv) {
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
noGitInit: false,
|
|
573
|
-
noClipboard: false,
|
|
574
|
-
noOpen: false,
|
|
575
|
-
yes: false,
|
|
576
|
-
// null means "not said", which is not the same as "none". Only the first
|
|
577
|
-
// suppresses the question.
|
|
578
|
-
agents: null,
|
|
579
|
-
error: null,
|
|
580
|
-
};
|
|
623
|
+
if (argv.some((argument) => argument === "-v" || argument === "--version")) {
|
|
624
|
+
return { ...NO_OPTIONS, version: true };
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
const options = { ...NO_OPTIONS };
|
|
581
628
|
|
|
582
629
|
for (let index = 0; index < argv.length; index += 1) {
|
|
583
630
|
const argument = argv[index];
|
|
@@ -988,15 +1035,16 @@ export function formatFindings(findings, { theme = createTheme() } = {}) {
|
|
|
988
1035
|
function report(args) {
|
|
989
1036
|
// Two renderings, kept adjacent on purpose.
|
|
990
1037
|
//
|
|
991
|
-
//
|
|
992
|
-
//
|
|
993
|
-
//
|
|
994
|
-
//
|
|
995
|
-
//
|
|
1038
|
+
// What is reported is decided once, in `summarize`: both functions below read
|
|
1039
|
+
// the same outcome and neither recounts anything. What remains separate is how
|
|
1040
|
+
// it looks. `contractReport` owes byte-for-byte what 1.4.1 printed, to scripts
|
|
1041
|
+
// that parse it; `expressiveReport` owes a person a legible hierarchy. Merging
|
|
1042
|
+
// them would mean one function whose every line carries a conditional, and the
|
|
1043
|
+
// first wording improvement would silently break somebody's grep.
|
|
996
1044
|
//
|
|
997
|
-
//
|
|
998
|
-
//
|
|
999
|
-
//
|
|
1045
|
+
// So a new reported fact belongs in the outcome, and reaches both renderings
|
|
1046
|
+
// from there; only its wording has to be chosen twice. They are written next
|
|
1047
|
+
// to each other so that editing one is an obvious prompt to consider the other.
|
|
1000
1048
|
if (args.theme.tier === "contract") return contractReport(args);
|
|
1001
1049
|
return expressiveReport(args);
|
|
1002
1050
|
}
|
|
@@ -1008,7 +1056,7 @@ function report(args) {
|
|
|
1008
1056
|
* `test/non-interactive.test.mjs` and by a capture-and-compare against the
|
|
1009
1057
|
* published package, because a script somewhere is reading it.
|
|
1010
1058
|
*/
|
|
1011
|
-
function contractReport({
|
|
1059
|
+
function contractReport({ outcome, harnesses, customTools, cwd, gitRoot, options, out, err, theme }) {
|
|
1012
1060
|
const lines = [];
|
|
1013
1061
|
const verb = options.dryRun ? "Would install" : "Installed";
|
|
1014
1062
|
|
|
@@ -1018,25 +1066,24 @@ function contractReport({ result, plan, adapters, harnesses, customTools, cwd, g
|
|
|
1018
1066
|
}
|
|
1019
1067
|
lines.push("");
|
|
1020
1068
|
|
|
1021
|
-
const written
|
|
1069
|
+
const { written, overwritten, skipped } = outcome;
|
|
1022
1070
|
lines.push(` ${written} file${plural(written)} ${options.dryRun ? "to write" : "written"}`);
|
|
1023
1071
|
|
|
1024
|
-
if (
|
|
1025
|
-
lines.push(` ${
|
|
1072
|
+
if (overwritten > 0) {
|
|
1073
|
+
lines.push(` ${overwritten} file${plural(overwritten)} overwritten (--force)`);
|
|
1026
1074
|
}
|
|
1027
1075
|
|
|
1028
|
-
lines.push(...contractAdapterLines({
|
|
1076
|
+
lines.push(...contractAdapterLines({ outcome, options, theme }));
|
|
1029
1077
|
lines.push(...customToolLines(customTools));
|
|
1030
1078
|
|
|
1031
|
-
const skipped = plan.filter((item) => item.status === "skip");
|
|
1032
1079
|
if (skipped.length > 0) {
|
|
1033
1080
|
lines.push(` ${skipped.length} file${plural(skipped.length)} left untouched because they already exist:`);
|
|
1034
|
-
for (const
|
|
1081
|
+
for (const path of skipped) lines.push(` ${path}`);
|
|
1035
1082
|
lines.push("");
|
|
1036
1083
|
lines.push(" Nothing above was modified. Re-run with --force to replace them.");
|
|
1037
1084
|
}
|
|
1038
1085
|
|
|
1039
|
-
if (
|
|
1086
|
+
if (outcome.alreadyInstalled) {
|
|
1040
1087
|
lines.push("");
|
|
1041
1088
|
lines.push("The kit is already installed here.");
|
|
1042
1089
|
}
|
|
@@ -1054,7 +1101,7 @@ function contractReport({ result, plan, adapters, harnesses, customTools, cwd, g
|
|
|
1054
1101
|
|
|
1055
1102
|
out(lines.join("\n") + "\n");
|
|
1056
1103
|
|
|
1057
|
-
const failures =
|
|
1104
|
+
const { failures } = outcome;
|
|
1058
1105
|
if (failures.length > 0) {
|
|
1059
1106
|
const detail = failures.map((error) => ` ${error.relativePath}: ${error.message}`).join("\n");
|
|
1060
1107
|
err(`\ncreate-pathfinder: ${failures.length} file${plural(failures.length)} could not be written:\n${detail}\n`);
|
|
@@ -1198,10 +1245,12 @@ async function offerEditor({ editors, cwd, prompter, out, env, platform, theme }
|
|
|
1198
1245
|
* Empty when no harness was chosen, which is the default and must stay
|
|
1199
1246
|
* invisible: a scripted 1.4.1-era run prints exactly what it always did.
|
|
1200
1247
|
*/
|
|
1201
|
-
function contractAdapterLines({
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1248
|
+
function contractAdapterLines({ outcome, options, theme }) {
|
|
1249
|
+
// No explicit "no harness chosen" guard: that case is no rows and
|
|
1250
|
+
// `blocked: false`, so it falls straight through to an empty list. The
|
|
1251
|
+
// blocked case is checked first because it is *also* no rows, and the two
|
|
1252
|
+
// must not print the same nothing.
|
|
1253
|
+
if (outcome.blocked) {
|
|
1205
1254
|
return [
|
|
1206
1255
|
"",
|
|
1207
1256
|
" No adapters were generated, because the kit copy did not finish.",
|
|
@@ -1210,21 +1259,9 @@ function contractAdapterLines({ adapters, harnesses, options, theme }) {
|
|
|
1210
1259
|
];
|
|
1211
1260
|
}
|
|
1212
1261
|
|
|
1213
|
-
const failed = new Set(adapters.result.errors.map((error) => error.relativePath));
|
|
1214
1262
|
const lines = [];
|
|
1215
1263
|
|
|
1216
|
-
for (const harness of
|
|
1217
|
-
const mine = adapters.plan.filter(
|
|
1218
|
-
(item) => item.harness === harness && !failed.has(item.relativePath),
|
|
1219
|
-
);
|
|
1220
|
-
const count = (action) => mine.filter((item) => item.action === action).length;
|
|
1221
|
-
|
|
1222
|
-
const generated = count("write");
|
|
1223
|
-
const replaced = count("replace");
|
|
1224
|
-
const unchanged = count("up-to-date");
|
|
1225
|
-
const conflicts = mine.filter((item) => item.action === "conflict");
|
|
1226
|
-
const orphans = mine.filter((item) => item.action === "orphan");
|
|
1227
|
-
|
|
1264
|
+
for (const { harness, generated, replaced, unchanged, conflicts, orphans } of outcome.harnessRows) {
|
|
1228
1265
|
lines.push(
|
|
1229
1266
|
` ${generated} ${harness.label} skill adapter${plural(generated)} ` +
|
|
1230
1267
|
(options.dryRun ? "to generate" : "generated"),
|
|
@@ -1242,7 +1279,7 @@ function contractAdapterLines({ adapters, harnesses, options, theme }) {
|
|
|
1242
1279
|
lines.push(
|
|
1243
1280
|
` ${conflicts.length} file${plural(conflicts.length)} left untouched because Pathfinder did not write ${conflicts.length === 1 ? "it" : "them"}:`,
|
|
1244
1281
|
);
|
|
1245
|
-
for (const
|
|
1282
|
+
for (const path of conflicts) lines.push(` ${path}`);
|
|
1246
1283
|
lines.push("");
|
|
1247
1284
|
lines.push(
|
|
1248
1285
|
conflicts.length === 1
|
|
@@ -1253,8 +1290,8 @@ function contractAdapterLines({ adapters, harnesses, options, theme }) {
|
|
|
1253
1290
|
);
|
|
1254
1291
|
}
|
|
1255
1292
|
|
|
1256
|
-
for (const
|
|
1257
|
-
lines.push(` ${
|
|
1293
|
+
for (const path of orphans) {
|
|
1294
|
+
lines.push(` ${path} delegates to a skill this version no longer`);
|
|
1258
1295
|
lines.push(" ships. It was left in place; delete it yourself if you want it gone.");
|
|
1259
1296
|
}
|
|
1260
1297
|
}
|
|
@@ -1311,7 +1348,7 @@ function customToolLines(customTools = []) {
|
|
|
1311
1348
|
* and get colour. The paths underneath get neither, for the reason below.
|
|
1312
1349
|
* - **Diagnostics stay pasteable.** See `pathList`.
|
|
1313
1350
|
*/
|
|
1314
|
-
function expressiveReport({
|
|
1351
|
+
function expressiveReport({ outcome, harnesses, customTools, cwd, gitRoot, options, out, err, theme }) {
|
|
1315
1352
|
const mark = theme.glyph;
|
|
1316
1353
|
const lines = [];
|
|
1317
1354
|
const verb = options.dryRun ? "Would install" : "Installed";
|
|
@@ -1325,7 +1362,7 @@ function expressiveReport({ result, plan, adapters, harnesses, customTools, cwd,
|
|
|
1325
1362
|
);
|
|
1326
1363
|
}
|
|
1327
1364
|
|
|
1328
|
-
const written
|
|
1365
|
+
const { written, overwritten, skipped } = outcome;
|
|
1329
1366
|
// A zero is reported, never celebrated. `✓ 0 files written` is a tick over
|
|
1330
1367
|
// nothing happening, which is the kind of detail that makes a whole summary
|
|
1331
1368
|
// feel automated rather than read.
|
|
@@ -1342,18 +1379,17 @@ function expressiveReport({ result, plan, adapters, harnesses, customTools, cwd,
|
|
|
1342
1379
|
// was asked to do, and marking a requested action as a warning is how a tool
|
|
1343
1380
|
// teaches people to ignore its warnings. The files it replaced are still
|
|
1344
1381
|
// worth stating plainly, which is what `info` is for.
|
|
1345
|
-
if (
|
|
1382
|
+
if (overwritten > 0) {
|
|
1346
1383
|
lines.push(
|
|
1347
1384
|
railed(
|
|
1348
1385
|
theme,
|
|
1349
|
-
theme.info(`${mark.info} ${
|
|
1386
|
+
theme.info(`${mark.info} ${overwritten} file${plural(overwritten)} overwritten (--force)`),
|
|
1350
1387
|
),
|
|
1351
1388
|
);
|
|
1352
1389
|
}
|
|
1353
1390
|
|
|
1354
|
-
lines.push(...expressiveAdapterLines({
|
|
1391
|
+
lines.push(...expressiveAdapterLines({ outcome, options, theme }));
|
|
1355
1392
|
|
|
1356
|
-
const skipped = plan.filter((item) => item.status === "skip");
|
|
1357
1393
|
if (skipped.length > 0) {
|
|
1358
1394
|
lines.push(
|
|
1359
1395
|
railed(
|
|
@@ -1379,33 +1415,26 @@ function expressiveReport({ result, plan, adapters, harnesses, customTools, cwd,
|
|
|
1379
1415
|
theme,
|
|
1380
1416
|
word: "Skipped",
|
|
1381
1417
|
summary: `${skipped.length} file${plural(skipped.length)} already exist${skipped.length === 1 ? "s" : ""} and ${skipped.length === 1 ? "was" : "were"} left untouched`,
|
|
1382
|
-
paths: skipped
|
|
1418
|
+
paths: skipped,
|
|
1383
1419
|
advice: ["Nothing above was modified. Re-run with --force to replace them."],
|
|
1384
1420
|
}),
|
|
1385
1421
|
);
|
|
1386
1422
|
}
|
|
1387
1423
|
|
|
1388
|
-
lines.push(...expressiveAdapterBlocks({
|
|
1424
|
+
lines.push(...expressiveAdapterBlocks({ outcome, theme }));
|
|
1389
1425
|
|
|
1390
1426
|
if (customTools.length > 0) lines.push(...customToolLines(customTools));
|
|
1391
1427
|
|
|
1392
|
-
|
|
1393
|
-
//
|
|
1394
|
-
//
|
|
1395
|
-
|
|
1396
|
-
// thirty-six routine skips "things to look at" would turn the one number that
|
|
1397
|
-
// should mean something into noise nobody reads twice.
|
|
1398
|
-
const attention = adapters.blocked
|
|
1399
|
-
? 0
|
|
1400
|
-
: adapters.plan.filter((item) => item.action === "conflict" || item.action === "orphan").length;
|
|
1401
|
-
|
|
1402
|
-
if (failureCount === 0) {
|
|
1428
|
+
// `attention` is what actually wants a human: a contested path, or an adapter
|
|
1429
|
+
// pointing at a skill that is gone. Skipped files are deliberately not among
|
|
1430
|
+
// them — see `summarize`, which is where that decision now lives.
|
|
1431
|
+
if (outcome.failures.length === 0) {
|
|
1403
1432
|
lines.push("");
|
|
1404
1433
|
lines.push(
|
|
1405
1434
|
...readyBlock({
|
|
1406
1435
|
theme,
|
|
1407
|
-
headline: endingHeadline({ theme,
|
|
1408
|
-
detail: endingDetail({
|
|
1436
|
+
headline: endingHeadline({ theme, outcome, options }),
|
|
1437
|
+
detail: endingDetail({ outcome, harnesses, options }),
|
|
1409
1438
|
}),
|
|
1410
1439
|
);
|
|
1411
1440
|
}
|
|
@@ -1431,7 +1460,7 @@ function expressiveReport({ result, plan, adapters, harnesses, customTools, cwd,
|
|
|
1431
1460
|
|
|
1432
1461
|
out(lines.join("\n") + "\n");
|
|
1433
1462
|
|
|
1434
|
-
const failures =
|
|
1463
|
+
const { failures } = outcome;
|
|
1435
1464
|
if (failures.length > 0) {
|
|
1436
1465
|
// `bad`, not `warn`, and the distinction is the whole point of having both:
|
|
1437
1466
|
// everything above is an outcome somebody may want to know about, and this
|
|
@@ -1481,29 +1510,21 @@ function pathList(paths) {
|
|
|
1481
1510
|
}
|
|
1482
1511
|
|
|
1483
1512
|
/** The per-harness summary counts, on the gutter, each at its own severity. */
|
|
1484
|
-
function expressiveAdapterLines({
|
|
1513
|
+
function expressiveAdapterLines({ outcome, options, theme }) {
|
|
1485
1514
|
const mark = theme.glyph;
|
|
1486
|
-
if (harnesses.length === 0) return [];
|
|
1487
1515
|
|
|
1488
|
-
|
|
1516
|
+
// No "no harness chosen" guard: that case is no rows and `blocked: false`,
|
|
1517
|
+
// so it falls through to an empty list. Blocked is checked first because it
|
|
1518
|
+
// is also no rows, and the two must not print the same nothing.
|
|
1519
|
+
if (outcome.blocked) {
|
|
1489
1520
|
return [
|
|
1490
1521
|
railed(theme, theme.bad(`${mark.bad} No adapters were generated`) + theme.dim(" (the kit copy did not finish)")),
|
|
1491
1522
|
];
|
|
1492
1523
|
}
|
|
1493
1524
|
|
|
1494
|
-
const failed = new Set(adapters.result.errors.map((error) => error.relativePath));
|
|
1495
1525
|
const lines = [];
|
|
1496
1526
|
|
|
1497
|
-
for (const harness of
|
|
1498
|
-
const mine = adapters.plan.filter(
|
|
1499
|
-
(item) => item.harness === harness && !failed.has(item.relativePath),
|
|
1500
|
-
);
|
|
1501
|
-
const count = (action) => mine.filter((item) => item.action === action).length;
|
|
1502
|
-
|
|
1503
|
-
const generated = count("write");
|
|
1504
|
-
const replaced = count("replace");
|
|
1505
|
-
const unchanged = count("up-to-date");
|
|
1506
|
-
|
|
1527
|
+
for (const { harness, generated, replaced, unchanged, conflicts, orphans } of outcome.harnessRows) {
|
|
1507
1528
|
lines.push(
|
|
1508
1529
|
railed(
|
|
1509
1530
|
theme,
|
|
@@ -1526,9 +1547,6 @@ function expressiveAdapterLines({ adapters, harnesses, options, theme }) {
|
|
|
1526
1547
|
);
|
|
1527
1548
|
}
|
|
1528
1549
|
|
|
1529
|
-
const conflicts = mine.filter((item) => item.action === "conflict");
|
|
1530
|
-
const orphans = mine.filter((item) => item.action === "orphan");
|
|
1531
|
-
|
|
1532
1550
|
if (conflicts.length > 0) {
|
|
1533
1551
|
lines.push(
|
|
1534
1552
|
railed(
|
|
@@ -1554,20 +1572,12 @@ function expressiveAdapterLines({ adapters, harnesses, options, theme }) {
|
|
|
1554
1572
|
}
|
|
1555
1573
|
|
|
1556
1574
|
/** The conflict and orphan detail blocks, with their paths kept pasteable. */
|
|
1557
|
-
function expressiveAdapterBlocks({
|
|
1558
|
-
if (
|
|
1575
|
+
function expressiveAdapterBlocks({ outcome, theme }) {
|
|
1576
|
+
if (outcome.blocked) return [];
|
|
1559
1577
|
|
|
1560
|
-
const failed = new Set(adapters.result.errors.map((error) => error.relativePath));
|
|
1561
1578
|
const blocks = [];
|
|
1562
1579
|
|
|
1563
|
-
for (const harness of
|
|
1564
|
-
const mine = adapters.plan.filter(
|
|
1565
|
-
(item) => item.harness === harness && !failed.has(item.relativePath),
|
|
1566
|
-
);
|
|
1567
|
-
|
|
1568
|
-
const conflicts = mine.filter((item) => item.action === "conflict");
|
|
1569
|
-
const orphans = mine.filter((item) => item.action === "orphan");
|
|
1570
|
-
|
|
1580
|
+
for (const { harness, conflicts, orphans } of outcome.harnessRows) {
|
|
1571
1581
|
if (conflicts.length > 0) {
|
|
1572
1582
|
const one = conflicts.length === 1;
|
|
1573
1583
|
blocks.push(
|
|
@@ -1575,7 +1585,7 @@ function expressiveAdapterBlocks({ adapters, harnesses, theme }) {
|
|
|
1575
1585
|
theme,
|
|
1576
1586
|
word: "Conflict",
|
|
1577
1587
|
summary: `${conflicts.length} ${harness.label} file${plural(conflicts.length)} at ${one ? "a path an adapter wants" : "paths adapters want"}, which Pathfinder did not write`,
|
|
1578
|
-
paths: conflicts
|
|
1588
|
+
paths: conflicts,
|
|
1579
1589
|
advice: [
|
|
1580
1590
|
`Re-run with --force to replace ${one ? "it" : "them"} ${theme.glyph.dash} note that --force also`,
|
|
1581
1591
|
"overwrites Pathfinder kit files you have edited.",
|
|
@@ -1591,7 +1601,7 @@ function expressiveAdapterBlocks({ adapters, harnesses, theme }) {
|
|
|
1591
1601
|
theme,
|
|
1592
1602
|
word: "Orphan",
|
|
1593
1603
|
summary: `${orphans.length} ${harness.label} adapter${plural(orphans.length)} delegat${one ? "es" : "e"} to a skill this version no longer ships`,
|
|
1594
|
-
paths: orphans
|
|
1604
|
+
paths: orphans,
|
|
1595
1605
|
advice: [
|
|
1596
1606
|
`Left in place. Delete ${one ? "it" : "them"} yourself if you want ${one ? "it" : "them"} gone.`,
|
|
1597
1607
|
],
|
package/src/kit.mjs
CHANGED
|
@@ -68,7 +68,7 @@ export function isExcluded(basename) {
|
|
|
68
68
|
*
|
|
69
69
|
* `context/current-feature.md` and `context/handoff.md` are transient session
|
|
70
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
|
|
71
|
+
* stopped shipping a blank `current-feature.md` stencil, because `/feature load`
|
|
72
72
|
* writes the real one on first use and a placeholder is just a file a reader
|
|
73
73
|
* has to recognise as empty. Having stopped shipping the blank one, the thing
|
|
74
74
|
* to guard against is shipping a *filled-in* one — a destination project
|
package/src/outcome.mjs
ADDED
|
@@ -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,23 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: complete-feature
|
|
3
|
-
description: Complete accepted work through the project's delivery workflow and durable records.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Complete Feature
|
|
7
|
-
|
|
8
|
-
Complete only work the human has accepted.
|
|
9
|
-
|
|
10
|
-
1. Confirm the Feature is accepted.
|
|
11
|
-
2. Run the project's required final checks.
|
|
12
|
-
3. Follow the documented Git, merge, version, release, and deployment workflow.
|
|
13
|
-
Ask for approval where required.
|
|
14
|
-
4. Mark the Feature complete.
|
|
15
|
-
5. Add a compact entry to `context/history.md`.
|
|
16
|
-
Create it from `templates/history.template.md` if it does not exist.
|
|
17
|
-
6. Clear or update `context/current-feature.md`. Do nothing if it does not exist.
|
|
18
|
-
7. If work tracking is configured, sync the completed state.
|
|
19
|
-
8. Report the completed outcome and any remaining follow-up.
|
|
20
|
-
|
|
21
|
-
Do not re-review accepted work unless final verification exposes a new problem.
|
|
22
|
-
|
|
23
|
-
Do not claim completion if required checks or delivery steps failed.
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: load-feature
|
|
3
|
-
description: Load one approved Feature and the minimum context needed to work on it.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Load Feature
|
|
7
|
-
|
|
8
|
-
1. Select the requested approved Feature. Its spec is
|
|
9
|
-
`NN-feature-name.md` in the spec source, and `NN` is its Feature number.
|
|
10
|
-
2. Read the Feature spec.
|
|
11
|
-
3. Read only the files or context needed for the current work.
|
|
12
|
-
4. Inspect the current Git state.
|
|
13
|
-
5. Stop if a required human decision or explicit dependency blocks the work.
|
|
14
|
-
6. Create or update `context/current-feature.md` — it does not ship, so the
|
|
15
|
-
first load writes it — with:
|
|
16
|
-
- Feature number, name, and spec path
|
|
17
|
-
- active delivery chunk
|
|
18
|
-
- Git state
|
|
19
|
-
- blocker, if any
|
|
20
|
-
- next action
|
|
21
|
-
7. If `context/tracker.md` exists, name the tracked item for this Feature —
|
|
22
|
-
its key is that Feature number. Do nothing here if it does not.
|
|
23
|
-
8. Present a short readiness summary.
|
|
24
|
-
|
|
25
|
-
Do not implement the Feature.
|
|
26
|
-
|
|
27
|
-
Do not scan unrelated repository areas, load history or roadmap by default,
|
|
28
|
-
rewrite the Feature, or silently resolve `TBD` decisions.
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: start-feature
|
|
3
|
-
description: Implement the active Feature in small, stable increments.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Start Feature
|
|
7
|
-
|
|
8
|
-
Implement the active delivery chunk.
|
|
9
|
-
|
|
10
|
-
1. Read only the context needed for the current work.
|
|
11
|
-
2. Restate the pre-implementation summary the project's agent guide requires,
|
|
12
|
-
and wait for it to be answered where it names a human decision. Do not edit
|
|
13
|
-
any file before this step is done.
|
|
14
|
-
3. Follow the Feature and existing project conventions.
|
|
15
|
-
4. Make the smallest complete change.
|
|
16
|
-
5. Verify the behavior you changed.
|
|
17
|
-
6. Keep the repository stable.
|
|
18
|
-
7. Create or update `context/current-feature.md` with the current state and next action.
|
|
19
|
-
|
|
20
|
-
## Rules
|
|
21
|
-
|
|
22
|
-
- Stay inside the approved scope.
|
|
23
|
-
- Stop if implementation requires a human decision or material scope change.
|
|
24
|
-
- Do not silently add dependencies or adopt prototype code.
|
|
25
|
-
- Follow the project's approval and Git rules.
|
|
26
|
-
- Do not decide that your own work is accepted.
|
|
27
|
-
|
|
28
|
-
When the chunk is done, report what changed, what was verified, and anything
|
|
29
|
-
unresolved.
|