projectstore-claude 0.29.3 → 0.29.4
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/bin/projectstore-claude.mjs +1 -1
- package/node_modules/projectstore/.claude-plugin/marketplace.json +1 -1
- package/node_modules/projectstore/.claude-plugin/plugin.json +1 -1
- package/node_modules/projectstore/README.md +17 -0
- package/node_modules/projectstore/commands/bind.md +6 -17
- package/node_modules/projectstore/commands/doctor.md +12 -7
- package/node_modules/projectstore/commands/statusline.md +1 -1
- package/node_modules/projectstore/commands/story.md +15 -2
- package/node_modules/projectstore/docs/how-it-works.md +5 -3
- package/node_modules/projectstore/harnesses/claude-code.json +5 -5
- package/node_modules/projectstore/harnesses/codex.json +4 -5
- package/node_modules/projectstore/hooks/band.tsx +48 -20
- package/node_modules/projectstore/hooks/pre-compact.mjs +7 -10
- package/node_modules/projectstore/hooks/session-rules.mjs +14 -8
- package/node_modules/projectstore/hooks/session-start.mjs +50 -18
- package/node_modules/projectstore/hooks/session-stop.mjs +8 -6
- package/node_modules/projectstore/package.json +11 -2
- package/node_modules/projectstore/scripts/band-shared.mjs +141 -24
- package/node_modules/projectstore/scripts/band.mjs +72 -21
- package/node_modules/projectstore/scripts/binding.mjs +616 -85
- package/node_modules/projectstore/scripts/build-adapters.mjs +73 -29
- package/node_modules/projectstore/scripts/cli.mjs +1015 -185
- package/node_modules/projectstore/scripts/codemap.mjs +22 -7
- package/node_modules/projectstore/scripts/convergence.mjs +253 -83
- package/node_modules/projectstore/scripts/diff-refs.mjs +14 -8
- package/node_modules/projectstore/scripts/doctor-report.mjs +91 -23
- package/node_modules/projectstore/scripts/doctor.mjs +1317 -371
- package/node_modules/projectstore/scripts/draft.mjs +5 -2
- package/node_modules/projectstore/scripts/fetch-shell.mjs +106 -20
- package/node_modules/projectstore/scripts/graph.mjs +19 -13
- package/node_modules/projectstore/scripts/harness.mjs +123 -48
- package/node_modules/projectstore/scripts/install-harness.mjs +3511 -616
- package/node_modules/projectstore/scripts/kanban.mjs +2 -1
- package/node_modules/projectstore/scripts/lib.mjs +694 -159
- package/node_modules/projectstore/scripts/mcp.mjs +386 -105
- package/node_modules/projectstore/scripts/portable-registration.mjs +224 -51
- package/node_modules/projectstore/scripts/provenance.mjs +26 -10
- package/node_modules/projectstore/scripts/query.mjs +378 -80
- package/node_modules/projectstore/scripts/reconcile.mjs +55 -18
- package/node_modules/projectstore/scripts/run-host.mjs +62 -17
- package/node_modules/projectstore/scripts/scaffold.mjs +89 -28
- package/node_modules/projectstore/scripts/setup.mjs +987 -0
- package/node_modules/projectstore/scripts/stages.mjs +5 -1
- package/node_modules/projectstore/scripts/statusline-launcher.mjs +36 -11
- package/node_modules/projectstore/scripts/statusline.mjs +32 -14
- package/node_modules/projectstore/scripts/story-section.mjs +39 -17
- package/node_modules/projectstore/scripts/surfaces.mjs +416 -95
- package/node_modules/projectstore/scripts/term.mjs +382 -97
- package/node_modules/projectstore/scripts/tokens.mjs +106 -28
- package/node_modules/projectstore/scripts/touch-session.mjs +109 -70
- package/node_modules/projectstore/scripts/version-guard.mjs +74 -27
- package/node_modules/projectstore/scripts/worktree.mjs +16 -5
- package/package.json +2 -2
|
@@ -50,7 +50,8 @@ import { commandForm, doctorSummaryLine } from "./lib.mjs";
|
|
|
50
50
|
// - folds: notes of this check fold on a terminal — true for all of them,
|
|
51
51
|
// or a pattern for the ones it names. The offer checks (doctor.mjs's
|
|
52
52
|
// OFFER_CHECKS: upgrade, layout-legacy) never fold, so they have no row.
|
|
53
|
-
const row = ({ instance = null, title = null, fix = null, folds = false }) =>
|
|
53
|
+
const row = ({ instance = null, title = null, fix = null, folds = false }) =>
|
|
54
|
+
Object.freeze({ instance, title, fix, folds });
|
|
54
55
|
|
|
55
56
|
export const CHECKS = Object.freeze({
|
|
56
57
|
// Fixes: the checks whose messages name no command.
|
|
@@ -59,7 +60,7 @@ export const CHECKS = Object.freeze({
|
|
|
59
60
|
title: "code_refs path does not resolve inside the project",
|
|
60
61
|
fix: "make each code_refs path exist in the project, and keep a story's paths inside its epic's",
|
|
61
62
|
}),
|
|
62
|
-
|
|
63
|
+
wikilink: row({
|
|
63
64
|
instance: /^Dead wiki-link \[\[(.+)\]\]\.$/,
|
|
64
65
|
title: "Dead wiki-link",
|
|
65
66
|
fix: "make each [[link]] name exactly one existing artifact (correct the name, or qualify it with its folder); if the check was skipped, fix the error it names",
|
|
@@ -69,24 +70,26 @@ export const CHECKS = Object.freeze({
|
|
|
69
70
|
title: "Dead relative link",
|
|
70
71
|
fix: "point each relative link at a file that exists, or remove it",
|
|
71
72
|
}),
|
|
72
|
-
|
|
73
|
+
acceptance: row({ fix: "tick the criteria the work met, or move the story out of done" }),
|
|
73
74
|
"review-status": row({ fix: "set reviewed_at to the review's date, or clear review_status" }),
|
|
74
75
|
"epic-status": row({ fix: "close the open stories, or move the epic out of done" }),
|
|
75
|
-
"spec-links": row({
|
|
76
|
-
|
|
76
|
+
"spec-links": row({
|
|
77
|
+
fix: "make each spec's stories and each story's specs name existing artifacts that list each other back, written inline as specs: [\"id\"]",
|
|
78
|
+
}),
|
|
79
|
+
index: row({
|
|
77
80
|
instance: / is not listed in .+\/README\.md's index\.$/,
|
|
78
81
|
title: "Artifact is not listed in its folder's README index",
|
|
79
82
|
fix: { say: "rebuild the folder indexes from frontmatter", command: (env) => commandForm("reconcile", { env }) },
|
|
80
83
|
}),
|
|
81
84
|
"index-header": row({ fix: "rewrite the index header in a registered form, so reconcile can rebuild the index" }),
|
|
82
85
|
// Notes that fold on a terminal while their section has something to fix.
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
86
|
+
surface: row({ folds: /— current, last written by / }),
|
|
87
|
+
mcp: row({ folds: true }),
|
|
88
|
+
statusline: row({ folds: /^Base HUD present/ }),
|
|
89
|
+
harness: row({ folds: true }),
|
|
87
90
|
"plugin-registration": row({ folds: / registered from the npm package / }),
|
|
88
91
|
"spec-policy": row({ folds: true }),
|
|
89
|
-
|
|
92
|
+
identity: row({ folds: true }),
|
|
90
93
|
"artifact-name": row({ folds: true }),
|
|
91
94
|
});
|
|
92
95
|
|
|
@@ -106,11 +109,24 @@ const plural = (n, word) => `${n} ${word}${n === 1 ? "" : "s"}`;
|
|
|
106
109
|
// at a line's end with the path alone below; a space again once wrapped.
|
|
107
110
|
const GLUE = "";
|
|
108
111
|
|
|
109
|
-
const folderName = (p) =>
|
|
112
|
+
const folderName = (p) =>
|
|
113
|
+
p
|
|
114
|
+
? String(p)
|
|
115
|
+
.replace(/[\\/]+$/, "")
|
|
116
|
+
.split(/[\\/]/)
|
|
117
|
+
.pop() || null
|
|
118
|
+
: null;
|
|
110
119
|
const folds = (r, f) => f.level === "info" && Boolean(r.folds) && (r.folds === true || r.folds.test(f.message));
|
|
111
|
-
const capture = (r, f) => {
|
|
120
|
+
const capture = (r, f) => {
|
|
121
|
+
const m = r.instance.exec(f.message);
|
|
122
|
+
return m && m[1] !== undefined ? m[1] : null;
|
|
123
|
+
};
|
|
112
124
|
|
|
113
|
-
export function report(
|
|
125
|
+
export function report(
|
|
126
|
+
findings,
|
|
127
|
+
groups,
|
|
128
|
+
{ caps: c = PLAIN, verbose = false, version = null, project = null, env = process.env } = {},
|
|
129
|
+
) {
|
|
114
130
|
const paint = painter(c);
|
|
115
131
|
// The terminal's compaction (contract 11, "on a TTY only"): the cap, the
|
|
116
132
|
// folds and the short instance lines. Plain output prints everything, and
|
|
@@ -122,29 +138,76 @@ export function report(findings, groups, { caps: c = PLAIN, verbose = false, ver
|
|
|
122
138
|
// first indented two further; one line anywhere else.
|
|
123
139
|
const prose = (n, text) => {
|
|
124
140
|
const pad = " ".repeat(n);
|
|
125
|
-
return wrap(text, width, pad + " ")
|
|
141
|
+
return wrap(text, width, pad + " ")
|
|
142
|
+
.split("\n")
|
|
143
|
+
.map((l, k) => (k ? l : pad + l).split(GLUE).join(" "));
|
|
126
144
|
};
|
|
127
|
-
const withPath = (text, f) =>
|
|
145
|
+
const withPath = (text, f) =>
|
|
146
|
+
f.file ? `${text} ${paint("explanation", dash)}${GLUE}${paint("explanation", f.file)}` : text;
|
|
128
147
|
|
|
129
148
|
function entries(level, check, r, these) {
|
|
130
149
|
const [, glyph, role] = LEVELS.find(([l]) => l === level);
|
|
131
150
|
const lead = `${paint(role, icon(c, glyph))} [${check}]`;
|
|
132
|
-
|
|
151
|
+
// A message with further lines (leads, commands) is never grouped: an
|
|
152
|
+
// instance row draws the message whole with its path after it, which
|
|
153
|
+
// would put the path after a command.
|
|
154
|
+
const matched = r.instance
|
|
155
|
+
? these.filter((f) => !f.about && !String(f.message).includes("\n") && r.instance.test(f.message))
|
|
156
|
+
: [];
|
|
133
157
|
const grouped = matched.length >= 2 ? matched : [];
|
|
134
158
|
const out = [];
|
|
135
159
|
for (const f of these) {
|
|
136
|
-
|
|
160
|
+
// A message with further lines (a remedy, the silenced copy's way back
|
|
161
|
+
// and `setup --channel npm`): its first line is prose, drawn with the
|
|
162
|
+
// path as one entry; the path goes before that line's closing colon, so
|
|
163
|
+
// the colon introduces what follows, not the path. After it, a line
|
|
164
|
+
// ending in `:` is a lead, drawn as prose after a blank line at the
|
|
165
|
+
// entry's text indent; any other line is a command, drawn alone after a
|
|
166
|
+
// blank line, six spaces in (contract 6; the story "User-facing text
|
|
167
|
+
// says where to run…", rule 4 as amended).
|
|
168
|
+
const [said, ...further] = String(f.message).split("\n");
|
|
169
|
+
if (!grouped.includes(f) && further.length) {
|
|
170
|
+
const colon = said.endsWith(":") ? ":" : "";
|
|
171
|
+
out.push(...prose(2, withPath(`${lead} ${colon ? said.slice(0, -1) : said}`, f) + colon));
|
|
172
|
+
for (const l of further)
|
|
173
|
+
out.push(...(l.endsWith(":") ? ["", ...prose(2, l)] : commandBlock(c, [l], { indent: 6 })));
|
|
174
|
+
out.push("");
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
if (!grouped.includes(f)) {
|
|
178
|
+
out.push(...prose(2, withPath(`${lead} ${f.message}`, f)));
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
137
181
|
if (f !== grouped[0]) continue;
|
|
138
182
|
out.push(...prose(2, `${lead} ${paint("emphasis", r.title)} (${grouped.length})`));
|
|
139
|
-
if (!compact) {
|
|
183
|
+
if (!compact) {
|
|
184
|
+
for (const g of grouped) out.push(...prose(6, withPath(g.message, g)));
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
140
187
|
// Each instance a row with its own glyph: its capture, then its path.
|
|
141
188
|
// A path moved beneath its capture (contract 5) reads `— <path>`, as
|
|
142
189
|
// plain's `message — file` does, so neither colour nor the indent is
|
|
143
190
|
// all that tells it from the next capture.
|
|
144
191
|
const shown = grouped.slice(0, INSTANCE_CAP);
|
|
145
|
-
const laid = rowLines(
|
|
146
|
-
|
|
147
|
-
|
|
192
|
+
const laid = rowLines(
|
|
193
|
+
c,
|
|
194
|
+
shown.map((g) => {
|
|
195
|
+
const cap = capture(r, g);
|
|
196
|
+
return {
|
|
197
|
+
glyph: "dot",
|
|
198
|
+
role: "explanation",
|
|
199
|
+
...(cap === null ? { target: g.file || g.message } : { target: cap, note: g.file || "" }),
|
|
200
|
+
};
|
|
201
|
+
}),
|
|
202
|
+
{ indent: 6 },
|
|
203
|
+
);
|
|
204
|
+
for (const [first, ...moved] of laid)
|
|
205
|
+
out.push(
|
|
206
|
+
first,
|
|
207
|
+
...moved.map((l, k) => (k ? l : l.replace(/^ */, (pad) => `${pad}${paint("explanation", dash)} `))),
|
|
208
|
+
);
|
|
209
|
+
if (grouped.length > shown.length)
|
|
210
|
+
out.push(` ${paint("explanation", `+${grouped.length - shown.length} more`)}`);
|
|
148
211
|
}
|
|
149
212
|
if (r.fix) {
|
|
150
213
|
const fix = typeof r.fix === "string" ? { say: r.fix, command: null } : r.fix;
|
|
@@ -169,11 +232,16 @@ export function report(findings, groups, { caps: c = PLAIN, verbose = false, ver
|
|
|
169
232
|
for (const check of [...new Set(atLevel.map((f) => f.check))]) {
|
|
170
233
|
const r = Object.hasOwn(CHECKS, check) ? CHECKS[check] : NONE;
|
|
171
234
|
let these = atLevel.filter((f) => f.check === check);
|
|
172
|
-
if (folding) {
|
|
235
|
+
if (folding) {
|
|
236
|
+
const kept = these.filter((f) => !folds(r, f));
|
|
237
|
+
hidden += these.length - kept.length;
|
|
238
|
+
these = kept;
|
|
239
|
+
}
|
|
173
240
|
if (these.length) out.push(...entries(level, check, r, these));
|
|
174
241
|
}
|
|
175
242
|
}
|
|
176
|
-
if (hidden)
|
|
243
|
+
if (hidden)
|
|
244
|
+
out.push(` ${paint("explanation", `${plural(hidden, "note")} hidden ${icon(c, "dot")} --verbose shows them`)}`);
|
|
177
245
|
while (out.at(-1) === "") out.pop();
|
|
178
246
|
return out;
|
|
179
247
|
}
|