carrick 0.3.80 → 0.3.82
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/carrick.mjs +47 -2
- package/dist/init/files.d.ts +1 -0
- package/dist/init/files.js +16 -0
- package/dist/init/files.js.map +1 -0
- package/dist/init/output.d.ts +20 -1
- package/dist/init/output.js +6 -2
- package/dist/init/output.js.map +1 -1
- package/dist/init/remove.js +36 -14
- package/dist/init/remove.js.map +1 -1
- package/dist/init/run.js +29 -11
- package/dist/init/run.js.map +1 -1
- package/dist/init/task-skills.d.ts +70 -0
- package/dist/init/task-skills.js +204 -0
- package/dist/init/task-skills.js.map +1 -0
- package/dist/scan.d.ts +160 -0
- package/dist/scan.js +415 -0
- package/dist/scan.js.map +1 -0
- package/dist/templates.d.ts +9 -0
- package/dist/templates.js +9 -1
- package/dist/templates.js.map +1 -1
- package/package.json +6 -6
- package/templates/skills/carrick-census.md +80 -0
- package/templates/skills/carrick-drift.md +101 -0
- package/templates/skills/carrick-impact.md +106 -0
- package/templates/skills/carrick-reuse.md +89 -0
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
// The four task skills `carrick init` installs beside the hooks.
|
|
2
|
+
//
|
|
3
|
+
// A skill is the body a hook or an agent's own judgement points at, and these
|
|
4
|
+
// four perform a task rather than remind: each one is a sequence of Carrick
|
|
5
|
+
// tool calls, and the agent's work is to confirm what came back in source,
|
|
6
|
+
// classify it, decide and act.
|
|
7
|
+
//
|
|
8
|
+
// `docs/reference/task-skills.md` is what each one covers, when it fires and
|
|
9
|
+
// how the stamp below decides who owns a file. The bodies themselves are the
|
|
10
|
+
// markdown in `templates/skills/`, so they can be read and reviewed as files.
|
|
11
|
+
//
|
|
12
|
+
// Two copies of the same bytes go in, one per harness: `.claude/skills/` for
|
|
13
|
+
// Claude Code and `.agents/skills/` for Codex, which is the scaffold parity
|
|
14
|
+
// rule of 2026-09-05.
|
|
15
|
+
//
|
|
16
|
+
// Every file this writes ends in a stamp: a marker and a digest of the body
|
|
17
|
+
// above it. The digest is what separates the three states a path can be in on
|
|
18
|
+
// a re-run — ours and untouched, ours and since edited, or somebody else's —
|
|
19
|
+
// and it stays right across an upgrade, where the shipped body changes and a
|
|
20
|
+
// version number would not. `carrick remove` deletes the untouched ones and
|
|
21
|
+
// nothing else, so a file a user has made their own is never destroyed by a
|
|
22
|
+
// command undoing an install.
|
|
23
|
+
//
|
|
24
|
+
// The reminder skill at `.claude/skills/carrick/SKILL.md` belongs to the cloud
|
|
25
|
+
// scaffold and carries no stamp, so nothing here reads or writes it.
|
|
26
|
+
import { createHash } from "node:crypto";
|
|
27
|
+
import fs from "node:fs";
|
|
28
|
+
import path from "node:path";
|
|
29
|
+
import { spawnSync } from "node:child_process";
|
|
30
|
+
import { templatesDir } from "../templates.js";
|
|
31
|
+
import { writeIfChanged } from "./files.js";
|
|
32
|
+
/** The skills, in the order `carrick init` reports them. */
|
|
33
|
+
export const TASK_SKILLS = [
|
|
34
|
+
"carrick-impact",
|
|
35
|
+
"carrick-reuse",
|
|
36
|
+
"carrick-drift",
|
|
37
|
+
"carrick-census",
|
|
38
|
+
];
|
|
39
|
+
/** One directory per harness. Both hold identical bytes. */
|
|
40
|
+
export const SKILL_ROOTS = [
|
|
41
|
+
path.join(".claude", "skills"),
|
|
42
|
+
path.join(".agents", "skills"),
|
|
43
|
+
];
|
|
44
|
+
/** Where one skill lands under one root, relative to the workspace. */
|
|
45
|
+
export function skillFile(root, name) {
|
|
46
|
+
return path.join(root, name, "SKILL.md");
|
|
47
|
+
}
|
|
48
|
+
/** Every path these skills occupy, relative to the workspace. */
|
|
49
|
+
export function taskSkillPaths() {
|
|
50
|
+
return SKILL_ROOTS.flatMap((root) => TASK_SKILLS.map((name) => skillFile(root, name)));
|
|
51
|
+
}
|
|
52
|
+
const STAMP = /^<!-- carrick:skill sha256:([0-9a-f]{12}) -->$/;
|
|
53
|
+
/** The digest of a body, as the stamp spells it. */
|
|
54
|
+
function digest(body) {
|
|
55
|
+
return createHash("sha256").update(body, "utf8").digest("hex").slice(0, 12);
|
|
56
|
+
}
|
|
57
|
+
/** A rendered body with its stamp on the last line. */
|
|
58
|
+
export function stamped(body) {
|
|
59
|
+
const text = body.endsWith("\n") ? body : `${body}\n`;
|
|
60
|
+
return `${text}\n<!-- carrick:skill sha256:${digest(text)} -->\n`;
|
|
61
|
+
}
|
|
62
|
+
export function skillState(existing) {
|
|
63
|
+
if (existing === null)
|
|
64
|
+
return "absent";
|
|
65
|
+
// Line endings are normalised before anything is compared. A checkout with
|
|
66
|
+
// `core.autocrlf` set rewrites every one of these files to CRLF on the way
|
|
67
|
+
// to disk, and a digest taken over those bytes would read all eight as
|
|
68
|
+
// somebody else's work and warn about them on every run.
|
|
69
|
+
const lines = existing.replace(/\r\n/g, "\n").split("\n");
|
|
70
|
+
// The marker is looked for anywhere rather than on the last line, because
|
|
71
|
+
// appending a step under it is as ordinary an edit as changing one above it,
|
|
72
|
+
// and a file that still carries our marker is still one of ours.
|
|
73
|
+
const marker = lines.findIndex((line) => STAMP.test(line));
|
|
74
|
+
if (marker === -1)
|
|
75
|
+
return "theirs";
|
|
76
|
+
// The blank line `stamped` puts before the marker is the join's last
|
|
77
|
+
// separator, so rejoining everything above it reproduces the exact bytes
|
|
78
|
+
// that were digested.
|
|
79
|
+
const body = lines.slice(0, marker).join("\n");
|
|
80
|
+
const after = lines.slice(marker + 1).join("").trim();
|
|
81
|
+
const claimed = STAMP.exec(lines[marker])?.[1];
|
|
82
|
+
return digest(body) === claimed && after === "" ? "ours" : "edited";
|
|
83
|
+
}
|
|
84
|
+
function scopeArgument(scope) {
|
|
85
|
+
return scope.slug === null ? 'repo: "<owner/repo>"' : `project: "${scope.slug}"`;
|
|
86
|
+
}
|
|
87
|
+
function scopeNote(scope) {
|
|
88
|
+
return scope.slug === null
|
|
89
|
+
? "Every Carrick call below needs a scope. Read this repository's once with `git remote get-url origin` and pass it as `repo: \"<owner/repo>\"` wherever the commands below write it."
|
|
90
|
+
: `Every Carrick call below carries \`project: "${scope.slug}"\`, which is this workspace's Carrick project and is already written into the commands.`;
|
|
91
|
+
}
|
|
92
|
+
/** One skill body, scoped and stamped, ready to write. */
|
|
93
|
+
export function renderTaskSkill(name, scope) {
|
|
94
|
+
const source = fs.readFileSync(path.join(templatesDir(), "skills", `${name}.md`), "utf8");
|
|
95
|
+
const rendered = source
|
|
96
|
+
.split("{{SCOPE_NOTE}}")
|
|
97
|
+
.join(scopeNote(scope))
|
|
98
|
+
.split("{{SCOPE}}")
|
|
99
|
+
.join(scopeArgument(scope));
|
|
100
|
+
// A body that still holds a placeholder is a file that looks written and
|
|
101
|
+
// tells an agent to call a tool with a literal `{{SCOPE}}` in it.
|
|
102
|
+
const left = /\{\{([A-Z_]+)\}\}/.exec(rendered);
|
|
103
|
+
if (left)
|
|
104
|
+
throw new Error(`${name} still holds ${left[0]} after rendering`);
|
|
105
|
+
return stamped(rendered);
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Write every skill under both roots, leaving alone anything that is not ours.
|
|
109
|
+
*
|
|
110
|
+
* Idempotent twice over: a second run renders the same bytes, and
|
|
111
|
+
* `writeIfChanged` does not touch a file that already holds them.
|
|
112
|
+
*/
|
|
113
|
+
export function writeTaskSkills(workspace, scope) {
|
|
114
|
+
const outcomes = [];
|
|
115
|
+
for (const root of SKILL_ROOTS) {
|
|
116
|
+
for (const name of TASK_SKILLS) {
|
|
117
|
+
const relative = skillFile(root, name);
|
|
118
|
+
const target = path.join(workspace, relative);
|
|
119
|
+
const existing = fs.existsSync(target) ? fs.readFileSync(target, "utf8") : null;
|
|
120
|
+
const state = skillState(existing);
|
|
121
|
+
if (state === "edited" || state === "theirs") {
|
|
122
|
+
outcomes.push({ path: relative, state, wrote: false });
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
const result = writeIfChanged(target, renderTaskSkill(name, scope));
|
|
126
|
+
outcomes.push({ path: relative, state, wrote: result === "written" });
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return outcomes;
|
|
130
|
+
}
|
|
131
|
+
/** The lines `carrick init` prints about what it just did with them. */
|
|
132
|
+
export function taskSkillLines(outcomes) {
|
|
133
|
+
const done = [];
|
|
134
|
+
const warn = [];
|
|
135
|
+
const installed = outcomes.filter((row) => row.state === "absent" || row.state === "ours");
|
|
136
|
+
if (installed.length > 0) {
|
|
137
|
+
done.push(`Task skills installed in ${SKILL_ROOTS.join(" and ")}: ${TASK_SKILLS.join(", ")}`);
|
|
138
|
+
}
|
|
139
|
+
for (const row of outcomes) {
|
|
140
|
+
if (row.state === "edited") {
|
|
141
|
+
warn.push(`${row.path} has been edited here, so it was left as it is. Delete it and run carrick init again for the current version.`);
|
|
142
|
+
}
|
|
143
|
+
else if (row.state === "theirs") {
|
|
144
|
+
warn.push(`${row.path} was not written by Carrick, so it was left as it is.`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return { done, warn };
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Delete the skill files this package wrote, and only those.
|
|
151
|
+
*
|
|
152
|
+
* An edited body and a file of somebody else's are returned as `kept` for the
|
|
153
|
+
* caller to name, exactly as the write path leaves them. A skill directory
|
|
154
|
+
* emptied by the deletion goes with it; a root holding anything else stays.
|
|
155
|
+
*/
|
|
156
|
+
export function removeTaskSkills(workspace) {
|
|
157
|
+
const deleted = [];
|
|
158
|
+
const kept = [];
|
|
159
|
+
for (const root of SKILL_ROOTS) {
|
|
160
|
+
for (const name of TASK_SKILLS) {
|
|
161
|
+
const relative = skillFile(root, name);
|
|
162
|
+
const target = path.join(workspace, relative);
|
|
163
|
+
if (!fs.existsSync(target))
|
|
164
|
+
continue;
|
|
165
|
+
const state = skillState(fs.readFileSync(target, "utf8"));
|
|
166
|
+
if (state !== "ours") {
|
|
167
|
+
kept.push({ path: relative, state, wrote: false });
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
fs.rmSync(target);
|
|
171
|
+
deleted.push(relative);
|
|
172
|
+
for (const directory of [path.dirname(target), path.join(workspace, root)]) {
|
|
173
|
+
try {
|
|
174
|
+
fs.rmdirSync(directory);
|
|
175
|
+
}
|
|
176
|
+
catch {
|
|
177
|
+
// Holds something else. That is the answer, not a failure.
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return { deleted, kept };
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* The skill roots this repository ignores.
|
|
186
|
+
*
|
|
187
|
+
* An ignored skill works on the machine that ran `carrick init` and reaches
|
|
188
|
+
* nobody else on the team, which is a thing to say once rather than a thing to
|
|
189
|
+
* change: what a repository ignores is its own decision.
|
|
190
|
+
*
|
|
191
|
+
* `git check-ignore` answers for a path that does not exist yet. Exit 0 is
|
|
192
|
+
* ignored and 1 is not; anything else (128 outside a repository) is git
|
|
193
|
+
* declining to answer, and this says nothing then.
|
|
194
|
+
*/
|
|
195
|
+
export function ignoredSkillRoots(workspace) {
|
|
196
|
+
return SKILL_ROOTS.filter((root) => {
|
|
197
|
+
const probe = spawnSync("git", ["check-ignore", "-q", "--", root], {
|
|
198
|
+
cwd: workspace,
|
|
199
|
+
stdio: "ignore",
|
|
200
|
+
});
|
|
201
|
+
return probe.status === 0;
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
//# sourceMappingURL=task-skills.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"task-skills.js","sourceRoot":"","sources":["../../src/init/task-skills.ts"],"names":[],"mappings":"AAAA,iEAAiE;AACjE,EAAE;AACF,8EAA8E;AAC9E,4EAA4E;AAC5E,2EAA2E;AAC3E,+BAA+B;AAC/B,EAAE;AACF,6EAA6E;AAC7E,6EAA6E;AAC7E,8EAA8E;AAC9E,EAAE;AACF,6EAA6E;AAC7E,4EAA4E;AAC5E,sBAAsB;AACtB,EAAE;AACF,4EAA4E;AAC5E,8EAA8E;AAC9E,6EAA6E;AAC7E,6EAA6E;AAC7E,4EAA4E;AAC5E,4EAA4E;AAC5E,8BAA8B;AAC9B,EAAE;AACF,+EAA+E;AAC/E,qEAAqE;AAErE,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAE/C,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAE5C,4DAA4D;AAC5D,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,gBAAgB;IAChB,eAAe;IACf,eAAe;IACf,gBAAgB;CACR,CAAC;AAIX,4DAA4D;AAC5D,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,QAAQ,CAAC;IAC9B,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,QAAQ,CAAC;CAC/B,CAAC;AAEF,uEAAuE;AACvE,MAAM,UAAU,SAAS,CAAC,IAAY,EAAE,IAAe;IACrD,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC;AAC3C,CAAC;AAED,iEAAiE;AACjE,MAAM,UAAU,cAAc;IAC5B,OAAO,WAAW,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;AACzF,CAAC;AAED,MAAM,KAAK,GAAG,gDAAgD,CAAC;AAE/D,oDAAoD;AACpD,SAAS,MAAM,CAAC,IAAY;IAC1B,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AAC9E,CAAC;AAED,uDAAuD;AACvD,MAAM,UAAU,OAAO,CAAC,IAAY;IAClC,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,IAAI,CAAC;IACtD,OAAO,GAAG,IAAI,+BAA+B,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC;AACpE,CAAC;AAYD,MAAM,UAAU,UAAU,CAAC,QAAuB;IAChD,IAAI,QAAQ,KAAK,IAAI;QAAE,OAAO,QAAQ,CAAC;IACvC,2EAA2E;IAC3E,2EAA2E;IAC3E,uEAAuE;IACvE,yDAAyD;IACzD,MAAM,KAAK,GAAG,QAAQ,CAAC,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC1D,0EAA0E;IAC1E,6EAA6E;IAC7E,iEAAiE;IACjE,MAAM,MAAM,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IAC3D,IAAI,MAAM,KAAK,CAAC,CAAC;QAAE,OAAO,QAAQ,CAAC;IACnC,qEAAqE;IACrE,yEAAyE;IACzE,sBAAsB;IACtB,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC/C,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IACtD,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAChD,OAAO,MAAM,CAAC,IAAI,CAAC,KAAK,OAAO,IAAI,KAAK,KAAK,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC;AACtE,CAAC;AAKD,SAAS,aAAa,CAAC,KAAiB;IACtC,OAAO,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,sBAAsB,CAAC,CAAC,CAAC,aAAa,KAAK,CAAC,IAAI,GAAG,CAAC;AACnF,CAAC;AAED,SAAS,SAAS,CAAC,KAAiB;IAClC,OAAO,KAAK,CAAC,IAAI,KAAK,IAAI;QACxB,CAAC,CAAC,oLAAoL;QACtL,CAAC,CAAC,gDAAgD,KAAK,CAAC,IAAI,0FAA0F,CAAC;AAC3J,CAAC;AAED,0DAA0D;AAC1D,MAAM,UAAU,eAAe,CAAC,IAAe,EAAE,KAAiB;IAChE,MAAM,MAAM,GAAG,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,EAAE,QAAQ,EAAE,GAAG,IAAI,KAAK,CAAC,EAAE,MAAM,CAAC,CAAC;IAC1F,MAAM,QAAQ,GAAG,MAAM;SACpB,KAAK,CAAC,gBAAgB,CAAC;SACvB,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;SACtB,KAAK,CAAC,WAAW,CAAC;SAClB,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,CAAC;IAC9B,yEAAyE;IACzE,kEAAkE;IAClE,MAAM,IAAI,GAAG,mBAAmB,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAChD,IAAI,IAAI;QAAE,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,gBAAgB,IAAI,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC;IAC5E,OAAO,OAAO,CAAC,QAAQ,CAAC,CAAC;AAC3B,CAAC;AAWD;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAAC,SAAiB,EAAE,KAAiB;IAClE,MAAM,QAAQ,GAAmB,EAAE,CAAC;IACpC,KAAK,MAAM,IAAI,IAAI,WAAW,EAAE,CAAC;QAC/B,KAAK,MAAM,IAAI,IAAI,WAAW,EAAE,CAAC;YAC/B,MAAM,QAAQ,GAAG,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;YACvC,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;YAC9C,MAAM,QAAQ,GAAG,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,YAAY,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;YAChF,MAAM,KAAK,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC;YACnC,IAAI,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,QAAQ,EAAE,CAAC;gBAC7C,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;gBACvD,SAAS;YACX,CAAC;YACD,MAAM,MAAM,GAAG,cAAc,CAAC,MAAM,EAAE,eAAe,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC;YACpE,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,KAAK,SAAS,EAAE,CAAC,CAAC;QACxE,CAAC;IACH,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,wEAAwE;AACxE,MAAM,UAAU,cAAc,CAAC,QAAwB;IACrD,MAAM,IAAI,GAAa,EAAE,CAAC;IAC1B,MAAM,IAAI,GAAa,EAAE,CAAC;IAC1B,MAAM,SAAS,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,KAAK,KAAK,QAAQ,IAAI,GAAG,CAAC,KAAK,KAAK,MAAM,CAAC,CAAC;IAC3F,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACzB,IAAI,CAAC,IAAI,CACP,4BAA4B,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CACnF,CAAC;IACJ,CAAC;IACD,KAAK,MAAM,GAAG,IAAI,QAAQ,EAAE,CAAC;QAC3B,IAAI,GAAG,CAAC,KAAK,KAAK,QAAQ,EAAE,CAAC;YAC3B,IAAI,CAAC,IAAI,CACP,GAAG,GAAG,CAAC,IAAI,+GAA+G,CAC3H,CAAC;QACJ,CAAC;aAAM,IAAI,GAAG,CAAC,KAAK,KAAK,QAAQ,EAAE,CAAC;YAClC,IAAI,CAAC,IAAI,CAAC,GAAG,GAAG,CAAC,IAAI,uDAAuD,CAAC,CAAC;QAChF,CAAC;IACH,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;AACxB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAAC,SAAiB;IAChD,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,MAAM,IAAI,GAAmB,EAAE,CAAC;IAChC,KAAK,MAAM,IAAI,IAAI,WAAW,EAAE,CAAC;QAC/B,KAAK,MAAM,IAAI,IAAI,WAAW,EAAE,CAAC;YAC/B,MAAM,QAAQ,GAAG,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;YACvC,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;YAC9C,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC;gBAAE,SAAS;YACrC,MAAM,KAAK,GAAG,UAAU,CAAC,EAAE,CAAC,YAAY,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;YAC1D,IAAI,KAAK,KAAK,MAAM,EAAE,CAAC;gBACrB,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;gBACnD,SAAS;YACX,CAAC;YACD,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;YAClB,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YACvB,KAAK,MAAM,SAAS,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC,EAAE,CAAC;gBAC3E,IAAI,CAAC;oBACH,EAAE,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;gBAC1B,CAAC;gBAAC,MAAM,CAAC;oBACP,2DAA2D;gBAC7D,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;AAC3B,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,iBAAiB,CAAC,SAAiB;IACjD,OAAO,WAAW,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE;QACjC,MAAM,KAAK,GAAG,SAAS,CAAC,KAAK,EAAE,CAAC,cAAc,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE;YACjE,GAAG,EAAE,SAAS;YACd,KAAK,EAAE,QAAQ;SAChB,CAAC,CAAC;QACH,OAAO,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC;IAC5B,CAAC,CAAC,CAAC;AACL,CAAC"}
|
package/dist/scan.d.ts
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { type InitOutput } from "./init/output.ts";
|
|
2
|
+
/** How far a build's phase has got. Mirrors `progress::PhaseState`. */
|
|
3
|
+
export type PhaseState = "started" | "done" | "warned";
|
|
4
|
+
/** Mirrors `progress::PhaseUpdate`. */
|
|
5
|
+
export type PhaseUpdate = {
|
|
6
|
+
label: string;
|
|
7
|
+
state: PhaseState;
|
|
8
|
+
};
|
|
9
|
+
/** Mirrors `progress::Update`: how far through a service's files a scan is. */
|
|
10
|
+
export type ProgressUpdate = {
|
|
11
|
+
service: string;
|
|
12
|
+
service_index: number;
|
|
13
|
+
service_total: number;
|
|
14
|
+
phase: "files" | "intents";
|
|
15
|
+
done: number;
|
|
16
|
+
total: number;
|
|
17
|
+
};
|
|
18
|
+
/** Mirrors `progress::ServiceSummary`. */
|
|
19
|
+
export type ServiceSummary = {
|
|
20
|
+
name: string;
|
|
21
|
+
routes: number;
|
|
22
|
+
calls: number;
|
|
23
|
+
/** Absent on a summary from a binary older than carrick#1321. */
|
|
24
|
+
functions?: number;
|
|
25
|
+
types?: number;
|
|
26
|
+
routes_without_response_type: number;
|
|
27
|
+
};
|
|
28
|
+
/** Mirrors `progress::Summary`: what a finished build amounts to. */
|
|
29
|
+
export type Summary = {
|
|
30
|
+
services: ServiceSummary[];
|
|
31
|
+
elapsed_secs: number;
|
|
32
|
+
next?: string[];
|
|
33
|
+
};
|
|
34
|
+
/** One line of a build's output, once it has been read. */
|
|
35
|
+
export type Marker = {
|
|
36
|
+
kind: "phase";
|
|
37
|
+
phase: PhaseUpdate;
|
|
38
|
+
} | {
|
|
39
|
+
kind: "progress";
|
|
40
|
+
update: ProgressUpdate;
|
|
41
|
+
} | {
|
|
42
|
+
kind: "summary";
|
|
43
|
+
summary: Summary;
|
|
44
|
+
} | {
|
|
45
|
+
kind: "notice";
|
|
46
|
+
text: string;
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* Read one line of a build's output as a marker, or answer null.
|
|
50
|
+
*
|
|
51
|
+
* Longest prefix wins, so a family whose names share a word cannot be read as
|
|
52
|
+
* one another — the same property `src/progress.rs` holds itself to. A marker
|
|
53
|
+
* whose payload is not the JSON this version expects is not a marker: the
|
|
54
|
+
* renderer draws nothing rather than drawing a guess.
|
|
55
|
+
*/
|
|
56
|
+
export declare function parseMarker(line: string): Marker | null;
|
|
57
|
+
/** The commands this renders. Everything else runs with its streams inherited. */
|
|
58
|
+
export declare const RENDERED: Set<string>;
|
|
59
|
+
/**
|
|
60
|
+
* Whether this invocation is rendered, or run as the binary writes it.
|
|
61
|
+
*
|
|
62
|
+
* `--verbose` is the escape hatch the closing line names, so it must hand the
|
|
63
|
+
* terminal straight to the binary. `--detach` answers with an id and leaves a
|
|
64
|
+
* log file behind — there is no run here to draw. `--help` is text.
|
|
65
|
+
*/
|
|
66
|
+
export declare function isRendered(command: string | undefined, args: string[]): boolean;
|
|
67
|
+
/** `169.4` as `2m49s`, `32.7` as `32.7s`. Under a minute keeps its tenth. */
|
|
68
|
+
export declare function elapsed(seconds: number): string;
|
|
69
|
+
/**
|
|
70
|
+
* The one line a finished build is worth: what the index holds, then the one
|
|
71
|
+
* shortfall in it.
|
|
72
|
+
*
|
|
73
|
+
* Routes and calls alone were the whole line, and a service of 111 routes,
|
|
74
|
+
* 363 functions and 175 types read as `111 routes · 1 call` — which to a
|
|
75
|
+
* developer, and to an agent, is a nearly empty index (carrick#1321). The two
|
|
76
|
+
* largest things it holds are named before the shortfall, because the line's
|
|
77
|
+
* subject is what was built.
|
|
78
|
+
*
|
|
79
|
+
* The shortfall is dropped when it is zero. A line states how many, of which
|
|
80
|
+
* thing, and what to do about it; nothing to do is nothing to say
|
|
81
|
+
* (carrick#1284).
|
|
82
|
+
*/
|
|
83
|
+
export declare function summaryLine(summary: Summary): string;
|
|
84
|
+
/** What a rendered run closes on: the next step, and nothing about cost. */
|
|
85
|
+
export declare function outroLine(summary: Summary): string;
|
|
86
|
+
/**
|
|
87
|
+
* The renderer, as a state machine over the child's lines.
|
|
88
|
+
*
|
|
89
|
+
* Separated from the spawn so a test drives it with a written stream and reads
|
|
90
|
+
* the interactive markers back — a terminal's own bytes are not capturable,
|
|
91
|
+
* and the plain rendering is a different renderer with different lines
|
|
92
|
+
* (carrick#1032).
|
|
93
|
+
*/
|
|
94
|
+
export declare class ScanRender {
|
|
95
|
+
private readonly output;
|
|
96
|
+
private readonly version;
|
|
97
|
+
/** Renders run in order, whichever event scheduled them. */
|
|
98
|
+
private chain;
|
|
99
|
+
/**
|
|
100
|
+
* The open step's own promise.
|
|
101
|
+
*
|
|
102
|
+
* Awaited at the head of every scheduled render and never inside the chain
|
|
103
|
+
* that starts it: a step ends when its phase ends, and a chain that waited
|
|
104
|
+
* on the step before running the line that ends it would wait forever.
|
|
105
|
+
*/
|
|
106
|
+
private stepDone;
|
|
107
|
+
private open;
|
|
108
|
+
/** The fraction the open phase last stated, for its spinner only. */
|
|
109
|
+
private running;
|
|
110
|
+
/** A finished phase whose line is not drawn yet: the summary may replace it. */
|
|
111
|
+
private held;
|
|
112
|
+
private opened;
|
|
113
|
+
private summarised;
|
|
114
|
+
/** Lines nobody rendered, for a failure and for nothing else. */
|
|
115
|
+
private readonly buffered;
|
|
116
|
+
/** The command's own answer, said before any phase began. */
|
|
117
|
+
private readonly answered;
|
|
118
|
+
constructor(output: InitOutput, version: string);
|
|
119
|
+
/** Whether anything at all was rendered. */
|
|
120
|
+
get rendered(): boolean;
|
|
121
|
+
/** What was not rendered, in the order it arrived. */
|
|
122
|
+
get unrendered(): string[];
|
|
123
|
+
/** One line of the child's stdout. */
|
|
124
|
+
stdout(line: string): void;
|
|
125
|
+
/** One line of the child's stderr. */
|
|
126
|
+
stderr(line: string): void;
|
|
127
|
+
private consume;
|
|
128
|
+
private begin;
|
|
129
|
+
private phase;
|
|
130
|
+
private phaseText;
|
|
131
|
+
/** Draw the held line, if there is one, and free the phase it belongs to. */
|
|
132
|
+
private close;
|
|
133
|
+
private progress;
|
|
134
|
+
private notice;
|
|
135
|
+
/** Say where an open phase is while it runs, without ending it. */
|
|
136
|
+
private redraw;
|
|
137
|
+
private summary;
|
|
138
|
+
/**
|
|
139
|
+
* The child is over. Answer whether anything was rendered, so a caller that
|
|
140
|
+
* rendered nothing can write the output through instead of eating it.
|
|
141
|
+
*/
|
|
142
|
+
finish(failed: boolean): Promise<boolean>;
|
|
143
|
+
private schedule;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Run the binary and render it.
|
|
147
|
+
*
|
|
148
|
+
* stdin stays inherited: a build asks nothing, but a child holding no stdin
|
|
149
|
+
* would break the moment one of them does.
|
|
150
|
+
*/
|
|
151
|
+
export declare function renderScan(options: {
|
|
152
|
+
binary: string;
|
|
153
|
+
args: string[];
|
|
154
|
+
env: NodeJS.ProcessEnv;
|
|
155
|
+
version: string;
|
|
156
|
+
output?: InitOutput;
|
|
157
|
+
}): Promise<{
|
|
158
|
+
code: number;
|
|
159
|
+
signal: NodeJS.Signals | null;
|
|
160
|
+
}>;
|