create-pathfinder 4.1.0 → 4.2.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/package.json +1 -1
- package/src/activation.mjs +199 -0
- package/src/cli.mjs +260 -6
- package/src/harnesses/adapter.mjs +42 -11
- package/src/harnesses/hook.mjs +142 -0
- package/src/harnesses/index.mjs +24 -5
- package/src/hooks/session-orientation.mjs +183 -0
- package/src/install.mjs +191 -3
- package/src/outcome.mjs +88 -36
package/src/outcome.mjs
CHANGED
|
@@ -38,11 +38,15 @@
|
|
|
38
38
|
* @param {{written: number, skipped: number, overwritten: number,
|
|
39
39
|
* errors: {relativePath: string, message: string}[]}} args.result
|
|
40
40
|
* @param {{plan: object[], result: object, blocked: boolean}} args.adapters
|
|
41
|
+
* @param {{plan: object[], result: object, blocked: boolean}} [args.hooks]
|
|
42
|
+
* the session hook handler plan and result, shaped exactly like `adapters`.
|
|
43
|
+
* Optional, and absent means "no handler was planned" — a harness with no
|
|
44
|
+
* lifecycle event has none, which is the ordinary case rather than an error.
|
|
41
45
|
* @param {{label: string}[]} args.harnesses the selected harnesses, registry order
|
|
42
46
|
* @param {{dryRun?: boolean}} args.options
|
|
43
47
|
* @returns {Readonly<object>} frozen; rows and lists frozen with it
|
|
44
48
|
*/
|
|
45
|
-
export function summarize({ plan, result, adapters, harnesses, options }) {
|
|
49
|
+
export function summarize({ plan, result, adapters, hooks = NO_HOOKS, harnesses, options }) {
|
|
46
50
|
// A dry run has no `result.written` to report, because nothing was written.
|
|
47
51
|
// The plan is counted instead, which is the same number the run would have
|
|
48
52
|
// produced had it been allowed to write.
|
|
@@ -54,9 +58,14 @@ export function summarize({ plan, result, adapters, harnesses, options }) {
|
|
|
54
58
|
.filter((item) => item.status === "skip")
|
|
55
59
|
.map((item) => item.relativePath);
|
|
56
60
|
|
|
57
|
-
// Copy errors before adapter errors, because that is
|
|
58
|
-
// in and the order the failure list has always
|
|
59
|
-
|
|
61
|
+
// Copy errors before adapter errors before handler errors, because that is
|
|
62
|
+
// the order they happened in and the order the failure list has always
|
|
63
|
+
// printed.
|
|
64
|
+
const failures = Object.freeze([
|
|
65
|
+
...result.errors,
|
|
66
|
+
...adapters.result.errors,
|
|
67
|
+
...hooks.result.errors,
|
|
68
|
+
]);
|
|
60
69
|
|
|
61
70
|
return Object.freeze({
|
|
62
71
|
written,
|
|
@@ -71,11 +80,35 @@ export function summarize({ plan, result, adapters, harnesses, options }) {
|
|
|
71
80
|
// that errored and this does not, so the two disagree exactly when a write
|
|
72
81
|
// fails — and this is the number the closing headline speaks for.
|
|
73
82
|
built: adapters.result.generated + adapters.result.replaced,
|
|
74
|
-
|
|
75
|
-
|
|
83
|
+
// Counted apart from `built`, and never folded into it. A handler is not
|
|
84
|
+
// an adapter — it delegates to nothing and is inert until a human
|
|
85
|
+
// activates it — so adding one to the adapter count would make the closing
|
|
86
|
+
// line state a number that is not true.
|
|
87
|
+
handlers: hooks.result.generated + hooks.result.replaced,
|
|
88
|
+
attention: attentionCount(adapters) + attentionCount(hooks),
|
|
89
|
+
harnessRows: harnessRows({ adapters, hooks, harnesses }),
|
|
76
90
|
});
|
|
77
91
|
}
|
|
78
92
|
|
|
93
|
+
/**
|
|
94
|
+
* What a run with no hook plan looks like.
|
|
95
|
+
*
|
|
96
|
+
* The same shape `generateHooks` returns, so a caller that has none and a
|
|
97
|
+
* caller whose harnesses have none reach identical code below.
|
|
98
|
+
*/
|
|
99
|
+
const NO_HOOKS = Object.freeze({
|
|
100
|
+
plan: Object.freeze([]),
|
|
101
|
+
result: Object.freeze({
|
|
102
|
+
generated: 0,
|
|
103
|
+
replaced: 0,
|
|
104
|
+
unchanged: 0,
|
|
105
|
+
conflicts: Object.freeze([]),
|
|
106
|
+
orphans: Object.freeze([]),
|
|
107
|
+
errors: Object.freeze([]),
|
|
108
|
+
}),
|
|
109
|
+
blocked: false,
|
|
110
|
+
});
|
|
111
|
+
|
|
79
112
|
/**
|
|
80
113
|
* What actually wants a human: a contested path, or an adapter pointing at a
|
|
81
114
|
* skill that is gone.
|
|
@@ -89,9 +122,9 @@ export function summarize({ plan, result, adapters, harnesses, options }) {
|
|
|
89
122
|
* errored paths included, because a path that could not be written is still a
|
|
90
123
|
* path somebody has to go and look at.
|
|
91
124
|
*/
|
|
92
|
-
function attentionCount(
|
|
93
|
-
if (
|
|
94
|
-
return
|
|
125
|
+
function attentionCount(generated) {
|
|
126
|
+
if (generated.blocked) return 0;
|
|
127
|
+
return generated.plan.filter(
|
|
95
128
|
(item) => item.action === "conflict" || item.action === "orphan",
|
|
96
129
|
).length;
|
|
97
130
|
}
|
|
@@ -110,38 +143,57 @@ function attentionCount(adapters) {
|
|
|
110
143
|
* empty plan, so that a harness which was chosen and never reached is never
|
|
111
144
|
* described as having generated zero adapters.
|
|
112
145
|
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
* reported once, as failures.
|
|
146
|
+
* Each row carries a harness's adapters and, under `handlers`, that harness's
|
|
147
|
+
* hook handlers — the same five facts twice, because they are the same five
|
|
148
|
+
* questions about two different kinds of generated file.
|
|
117
149
|
*/
|
|
118
|
-
function harnessRows({ adapters, harnesses }) {
|
|
150
|
+
function harnessRows({ adapters, hooks, harnesses }) {
|
|
119
151
|
if (harnesses.length === 0 || adapters.blocked) return Object.freeze([]);
|
|
120
152
|
|
|
121
|
-
const failed = new Set(adapters.result.errors.map((error) => error.relativePath));
|
|
122
|
-
|
|
123
153
|
return Object.freeze(
|
|
124
|
-
harnesses.map((harness) =>
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
154
|
+
harnesses.map((harness) =>
|
|
155
|
+
Object.freeze({
|
|
156
|
+
harness,
|
|
157
|
+
...tally(adapters, harness),
|
|
158
|
+
// The same five facts for this harness's hook handlers, under their
|
|
159
|
+
// own names. A harness with no lifecycle event has no handlers, and
|
|
160
|
+
// every one of these is then zero or empty — which is what a report
|
|
161
|
+
// needs in order to say nothing at all about them.
|
|
162
|
+
handlers: Object.freeze(tally(hooks, harness)),
|
|
163
|
+
}),
|
|
164
|
+
),
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* One harness's share of a generated plan, as the five facts a report prints.
|
|
170
|
+
*
|
|
171
|
+
* Paths that failed to write are excluded from every count and list: a file
|
|
172
|
+
* that could not be written was not generated, is not up to date, and is not a
|
|
173
|
+
* conflict the user can resolve by re-running with `--force`. They are
|
|
174
|
+
* reported once, as failures.
|
|
175
|
+
*/
|
|
176
|
+
function tally(generated, harness) {
|
|
177
|
+
const failed = new Set(generated.result.errors.map((error) => error.relativePath));
|
|
178
|
+
|
|
179
|
+
// Identity, not label: the harness object on a plan item is the registry
|
|
180
|
+
// entry itself, and two entries could plausibly share a label one day.
|
|
181
|
+
const mine = generated.blocked
|
|
182
|
+
? []
|
|
183
|
+
: generated.plan.filter(
|
|
128
184
|
(item) => item.harness === harness && !failed.has(item.relativePath),
|
|
129
185
|
);
|
|
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
186
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
187
|
+
const count = (action) => mine.filter((item) => item.action === action).length;
|
|
188
|
+
const paths = (action) =>
|
|
189
|
+
Object.freeze(mine.filter((item) => item.action === action).map((item) => item.relativePath));
|
|
190
|
+
|
|
191
|
+
return {
|
|
192
|
+
generated: count("write"),
|
|
193
|
+
replaced: count("replace"),
|
|
194
|
+
unchanged: count("up-to-date"),
|
|
195
|
+
// Plan order, which is the order they will be printed in.
|
|
196
|
+
conflicts: paths("conflict"),
|
|
197
|
+
orphans: paths("orphan"),
|
|
198
|
+
};
|
|
147
199
|
}
|