etymd 0.19.1 → 0.19.3
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/CHANGELOG.md +19 -0
- package/dist/{approve-J6JU7MBX.js → approve-R24ZMJWB.js} +3 -3
- package/dist/audit-AZW4QGOE.js +11 -0
- package/dist/{brief-NHHILXIH.js → brief-HEJJU3GB.js} +3 -3
- package/dist/{chunk-UMKC3OWQ.js → chunk-5RRX2ONV.js} +3 -3
- package/dist/{chunk-GFUDKTC5.js → chunk-635GEE4V.js} +2 -2
- package/dist/{chunk-WKP72Z3J.js → chunk-7LRLA2XZ.js} +1 -1
- package/dist/{chunk-2DRA7S67.js → chunk-AXUYE7HH.js} +1 -1
- package/dist/{chunk-PQISPOB2.js → chunk-BXBSFDYL.js} +153 -64
- package/dist/{chunk-M7XDP6FT.js → chunk-IEHSNXR3.js} +4 -4
- package/dist/{chunk-KQFEKOON.js → chunk-VCEA7333.js} +1 -1
- package/dist/{chunk-6DJMKZGK.js → chunk-VVUEOA4V.js} +32 -15
- package/dist/{chunk-7N5JDXQG.js → chunk-XYJIDPKZ.js} +1 -1
- package/dist/cli.js +16 -16
- package/dist/{doctor-264Q5PLY.js → doctor-UGW5G5IZ.js} +6 -6
- package/dist/{fleet-VT625Q2J.js → fleet-Y22JBWLP.js} +8 -8
- package/dist/{gates-YUMSTKUG.js → gates-D6QPKN63.js} +5 -5
- package/dist/{generate-62GQSCKH.js → generate-5AF7V3DH.js} +3 -3
- package/dist/index.d.ts +1 -1
- package/dist/index.js +184 -78
- package/dist/{init-G4XXFXVD.js → init-FNLRAYP4.js} +12 -5
- package/dist/{premise-RNMAKMVA.js → premise-FYOWXHUH.js} +4 -4
- package/dist/{propose-35JXMEPH.js → propose-5XOMTLW7.js} +6 -6
- package/dist/{scan-PJH32H7C.js → scan-QBPWNLNC.js} +3 -3
- package/dist/scan-WPXLF2J4.js +5 -0
- package/package.json +1 -1
- package/dist/audit-5N2TU2X7.js +0 -11
- package/dist/scan-F6OW4ANK.js +0 -5
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { VERSION } from './chunk-
|
|
2
|
+
import { VERSION } from './chunk-VCEA7333.js';
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import { Command } from 'commander';
|
|
5
5
|
import pc from 'picocolors';
|
|
@@ -31,7 +31,7 @@ program.command("audit").description("Verify every instruction claim against the
|
|
|
31
31
|
"exit non-zero when findings at/above this tier exist (risk|gap|polish)"
|
|
32
32
|
).action(
|
|
33
33
|
(opts, cmd) => action(async () => {
|
|
34
|
-
const { run } = await import('./audit-
|
|
34
|
+
const { run } = await import('./audit-AZW4QGOE.js');
|
|
35
35
|
await run({
|
|
36
36
|
cwd: resolveCwd(cmd),
|
|
37
37
|
json: opts.json,
|
|
@@ -44,7 +44,7 @@ program.command("audit").description("Verify every instruction claim against the
|
|
|
44
44
|
);
|
|
45
45
|
program.command("init").description("Onboard the truth guard: approve the baseline; scaffold AGENTS.md only if asked").option("-y, --yes", "accept defaults without prompting (never overwrites)").option("--with-agents", "also scaffold a minimal AGENTS.md where none exists (off by default)").action(
|
|
46
46
|
(opts, cmd) => action(async () => {
|
|
47
|
-
const { run } = await import('./init-
|
|
47
|
+
const { run } = await import('./init-FNLRAYP4.js');
|
|
48
48
|
await run({ cwd: resolveCwd(cmd), yes: opts.yes, withAgents: opts.withAgents });
|
|
49
49
|
})
|
|
50
50
|
);
|
|
@@ -52,19 +52,19 @@ program.command("approve").description(
|
|
|
52
52
|
"Re-approve the committed baseline after intentional structural changes (non-interactive)"
|
|
53
53
|
).action(
|
|
54
54
|
(_opts, cmd) => action(async () => {
|
|
55
|
-
const { run } = await import('./approve-
|
|
55
|
+
const { run } = await import('./approve-R24ZMJWB.js');
|
|
56
56
|
await run({ cwd: resolveCwd(cmd) });
|
|
57
57
|
})
|
|
58
58
|
);
|
|
59
59
|
program.command("scan").description("Deterministically reckon the project into a facts index").option("--json", "print the raw facts as JSON").option("--no-save", "do not write the .etymd cache").action(
|
|
60
60
|
(opts, cmd) => action(async () => {
|
|
61
|
-
const { run } = await import('./scan-
|
|
61
|
+
const { run } = await import('./scan-QBPWNLNC.js');
|
|
62
62
|
await run({ cwd: resolveCwd(cmd), json: opts.json, save: opts.save });
|
|
63
63
|
})
|
|
64
64
|
);
|
|
65
65
|
program.command("doctor").description('Alias for `audit --truth` \u2014 "are the recorded instructions still true?"').option("--json", "print findings as JSON").action(
|
|
66
66
|
(opts, cmd) => action(async () => {
|
|
67
|
-
const { run } = await import('./doctor-
|
|
67
|
+
const { run } = await import('./doctor-UGW5G5IZ.js');
|
|
68
68
|
await run({ cwd: resolveCwd(cmd), json: opts.json });
|
|
69
69
|
})
|
|
70
70
|
);
|
|
@@ -76,7 +76,7 @@ program.command("context").description("Measure the always-loaded context footpr
|
|
|
76
76
|
);
|
|
77
77
|
program.command("brief").description("Emit a grounded briefing for the in-repo agent to complete the semantic layer").option("--human", "write a human onboarding brief instead of the agent briefing").action(
|
|
78
78
|
(opts, cmd) => action(async () => {
|
|
79
|
-
const { run } = await import('./brief-
|
|
79
|
+
const { run } = await import('./brief-HEJJU3GB.js');
|
|
80
80
|
await run({ cwd: resolveCwd(cmd), human: opts.human });
|
|
81
81
|
})
|
|
82
82
|
);
|
|
@@ -90,7 +90,7 @@ program.command("premise").argument("[task]", "the task you are about to hand an
|
|
|
90
90
|
"exit non-zero when findings at/above this tier exist (risk|gap|polish)"
|
|
91
91
|
).action(
|
|
92
92
|
(task, opts, cmd) => action(async () => {
|
|
93
|
-
const { run } = await import('./premise-
|
|
93
|
+
const { run } = await import('./premise-FYOWXHUH.js');
|
|
94
94
|
await run({
|
|
95
95
|
cwd: resolveCwd(cmd),
|
|
96
96
|
task,
|
|
@@ -132,7 +132,7 @@ var fleet = program.command("fleet").description(
|
|
|
132
132
|
"exit non-zero when findings at/above this tier exist (risk|gap|polish)"
|
|
133
133
|
).action(
|
|
134
134
|
(opts, cmd) => action(async () => {
|
|
135
|
-
const { sweep } = await import('./fleet-
|
|
135
|
+
const { sweep } = await import('./fleet-Y22JBWLP.js');
|
|
136
136
|
await sweep({
|
|
137
137
|
cwd: resolveCwd(cmd),
|
|
138
138
|
manifest: opts.manifest,
|
|
@@ -147,7 +147,7 @@ var fleet = program.command("fleet").description(
|
|
|
147
147
|
);
|
|
148
148
|
fleet.command("check").description("Validate the manifest pair only \u2014 no lenses: dangling mappings, duplicates, leaks").option("--manifest <file>", "the fleet manifest \u2014 required unless the cwd holds registry.json").option("--json", "print the findings as JSON (EXPERIMENTAL through 0.2.x)").action(
|
|
149
149
|
(_opts, cmd) => action(async () => {
|
|
150
|
-
const { check } = await import('./fleet-
|
|
150
|
+
const { check } = await import('./fleet-Y22JBWLP.js');
|
|
151
151
|
const opts = cmd.optsWithGlobals();
|
|
152
152
|
await check({ cwd: resolveCwd(cmd), manifest: opts.manifest, json: opts.json });
|
|
153
153
|
})
|
|
@@ -156,7 +156,7 @@ fleet.command("board").description(
|
|
|
156
156
|
"Render the fleet board: every project's milestones (contract key `milestones`) plus a ranked initiatives table"
|
|
157
157
|
).option("--manifest <file>", "the fleet manifest \u2014 required unless the cwd holds registry.json").option("--initiatives <file>", "the hand-edited initiatives table (rank | id | initiative | \u2026)").option("--out <file>", "write the Markdown board here instead of printing it").option("--json", "print the board as JSON (EXPERIMENTAL)").action(
|
|
158
158
|
(opts, cmd) => action(async () => {
|
|
159
|
-
const { board } = await import('./fleet-
|
|
159
|
+
const { board } = await import('./fleet-Y22JBWLP.js');
|
|
160
160
|
const shared = cmd.optsWithGlobals();
|
|
161
161
|
await board({
|
|
162
162
|
cwd: resolveCwd(cmd),
|
|
@@ -172,7 +172,7 @@ fleet.command("add").argument("<dir>", "directory of the project to register").d
|
|
|
172
172
|
"public-repo | public-bound | private \u2014 mandatory for personal entries"
|
|
173
173
|
).option("-y, --yes", "skip prompts; every mandatory value must be passed as a flag").option("--manifest <file>", "the fleet manifest \u2014 required unless the cwd holds registry.json").action(
|
|
174
174
|
(dir, opts, cmd) => action(async () => {
|
|
175
|
-
const { add } = await import('./fleet-
|
|
175
|
+
const { add } = await import('./fleet-Y22JBWLP.js');
|
|
176
176
|
const shared = cmd.optsWithGlobals();
|
|
177
177
|
await add({
|
|
178
178
|
cwd: resolveCwd(cmd),
|
|
@@ -195,7 +195,7 @@ fleet.command("dismiss").argument("<name>", "the registered project name").argum
|
|
|
195
195
|
"Dismiss a project's finding from any cwd \u2014 guarded ledgers persist beside the manifest"
|
|
196
196
|
).requiredOption("--reason <text>", "why it is dismissed \u2014 recorded so the decision survives").option("--manifest <file>", "the fleet manifest \u2014 required unless the cwd holds registry.json").action(
|
|
197
197
|
(name, id, _opts, cmd) => action(async () => {
|
|
198
|
-
const { dismiss } = await import('./fleet-
|
|
198
|
+
const { dismiss } = await import('./fleet-Y22JBWLP.js');
|
|
199
199
|
const opts = cmd.optsWithGlobals();
|
|
200
200
|
await dismiss({
|
|
201
201
|
cwd: resolveCwd(cmd),
|
|
@@ -210,7 +210,7 @@ fleet.command("accept").argument("<name>", "the registered project name").argume
|
|
|
210
210
|
"Accept a project's finding as a known trade-off \u2014 guarded ledgers persist beside the manifest"
|
|
211
211
|
).option("--reason <text>", "optional note on why the trade-off is accepted").option("--manifest <file>", "the fleet manifest \u2014 required unless the cwd holds registry.json").action(
|
|
212
212
|
(name, id, _opts, cmd) => action(async () => {
|
|
213
|
-
const { accept } = await import('./fleet-
|
|
213
|
+
const { accept } = await import('./fleet-Y22JBWLP.js');
|
|
214
214
|
const opts = cmd.optsWithGlobals();
|
|
215
215
|
await accept({ cwd: resolveCwd(cmd), manifest: opts.manifest, name, id, reason: opts.reason });
|
|
216
216
|
})
|
|
@@ -225,7 +225,7 @@ program.command("propose").description(
|
|
|
225
225
|
"the fleet manifest \u2014 runs a read-only sweep now (no delta baseline move)"
|
|
226
226
|
).option("--from <file>", "read a stored `etymd fleet --json` output instead of sweeping").option("--json", "print the proposal/1 records as JSON (EXPERIMENTAL)").action(
|
|
227
227
|
(opts, cmd) => action(async () => {
|
|
228
|
-
const { run } = await import('./propose-
|
|
228
|
+
const { run } = await import('./propose-5XOMTLW7.js');
|
|
229
229
|
await run({
|
|
230
230
|
cwd: resolveCwd(cmd),
|
|
231
231
|
manifest: opts.manifest,
|
|
@@ -256,7 +256,7 @@ program.command("screen").description(
|
|
|
256
256
|
);
|
|
257
257
|
program.command("gates").description("Install the local git-hook gates (process \u2192 pre-commit, correctness \u2192 pre-push)").option("--ci", "note about the CI review gate (ships later; local gates install now)").option("-y, --yes", "skip prompts; never overwrites a hand-edited hook").action(
|
|
258
258
|
(opts, cmd) => action(async () => {
|
|
259
|
-
const { run } = await import('./gates-
|
|
259
|
+
const { run } = await import('./gates-D6QPKN63.js');
|
|
260
260
|
await run({ cwd: resolveCwd(cmd), ci: opts.ci, yes: opts.yes });
|
|
261
261
|
})
|
|
262
262
|
);
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { run } from './chunk-
|
|
3
|
-
import './chunk-
|
|
4
|
-
import './chunk-
|
|
2
|
+
import { run } from './chunk-635GEE4V.js';
|
|
3
|
+
import './chunk-5RRX2ONV.js';
|
|
4
|
+
import './chunk-7LRLA2XZ.js';
|
|
5
5
|
import './chunk-3E2IPCRY.js';
|
|
6
6
|
import './chunk-HI7NWPRA.js';
|
|
7
|
-
import './chunk-
|
|
8
|
-
import './chunk-
|
|
7
|
+
import './chunk-VVUEOA4V.js';
|
|
8
|
+
import './chunk-VCEA7333.js';
|
|
9
9
|
import './chunk-P6ATKV2R.js';
|
|
10
|
-
import './chunk-
|
|
10
|
+
import './chunk-AXUYE7HH.js';
|
|
11
11
|
import './chunk-4VPBP6K6.js';
|
|
12
12
|
|
|
13
13
|
// src/commands/doctor.ts
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { dismiss, accept } from './chunk-HOR4M6EC.js';
|
|
3
|
-
import { sweepFleet, FLEET_JSON_SCHEMA, checkManifest, FLEET_TRUST_VALUES, isFleetTrust, MILESTONES_FILE, parseMilestones, parseInitiatives, BOARD_JSON_SCHEMA, renderBoard, loadFleetManifest, fleetLedgerTarget, ensureFindingRecorded } from './chunk-
|
|
4
|
-
import './chunk-
|
|
5
|
-
import { parseFailOnTier, meetsFailOn } from './chunk-
|
|
3
|
+
import { sweepFleet, FLEET_JSON_SCHEMA, checkManifest, FLEET_TRUST_VALUES, isFleetTrust, MILESTONES_FILE, parseMilestones, parseInitiatives, BOARD_JSON_SCHEMA, renderBoard, loadFleetManifest, fleetLedgerTarget, ensureFindingRecorded } from './chunk-IEHSNXR3.js';
|
|
4
|
+
import './chunk-5RRX2ONV.js';
|
|
5
|
+
import { parseFailOnTier, meetsFailOn } from './chunk-7LRLA2XZ.js';
|
|
6
6
|
import './chunk-3E2IPCRY.js';
|
|
7
|
-
import { generateClaudePointerMd } from './chunk-
|
|
7
|
+
import { generateClaudePointerMd } from './chunk-BXBSFDYL.js';
|
|
8
8
|
import { print, theme, section, renderFleetRows, renderFleetNotes, renderFindings, TIER_BADGE, glyph } from './chunk-HI7NWPRA.js';
|
|
9
|
-
import { checkClaudePointer, scanProject } from './chunk-
|
|
10
|
-
import './chunk-
|
|
9
|
+
import { checkClaudePointer, scanProject } from './chunk-VVUEOA4V.js';
|
|
10
|
+
import './chunk-VCEA7333.js';
|
|
11
11
|
import './chunk-P6ATKV2R.js';
|
|
12
|
-
import './chunk-
|
|
12
|
+
import './chunk-AXUYE7HH.js';
|
|
13
13
|
import { readText, pathExists, git } from './chunk-4VPBP6K6.js';
|
|
14
14
|
import { promises } from 'fs';
|
|
15
15
|
import os from 'os';
|
|
@@ -240,7 +240,7 @@ async function add(opts) {
|
|
|
240
240
|
throw new Error(
|
|
241
241
|
`\`${name}\` has a CLAUDE.md that hides its AGENTS.md from Claude Code (${pointer.detail}). Add a full-line \`@AGENTS.md\` import to it, e.g.:
|
|
242
242
|
|
|
243
|
-
${generateClaudePointerMd().trimEnd()
|
|
243
|
+
${generateClaudePointerMd().trimEnd()}
|
|
244
244
|
|
|
245
245
|
\u2014 or symlink either file to the other, or delete the CLAUDE.md \u2014 then re-run \`etymd fleet add\`.`
|
|
246
246
|
);
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { applyFiles } from './chunk-GWGKEPRX.js';
|
|
3
|
-
import { deriveFailOn, derivedCommands, planWorkflow } from './chunk-
|
|
4
|
-
import { runPrefix, isSafeGateCommand } from './chunk-
|
|
3
|
+
import { deriveFailOn, derivedCommands, planWorkflow } from './chunk-XYJIDPKZ.js';
|
|
4
|
+
import { runPrefix, isSafeGateCommand } from './chunk-BXBSFDYL.js';
|
|
5
5
|
import { section, print, glyph, theme, renderPlan } from './chunk-HI7NWPRA.js';
|
|
6
|
-
import { scanProject } from './chunk-
|
|
7
|
-
import './chunk-
|
|
6
|
+
import { scanProject } from './chunk-VVUEOA4V.js';
|
|
7
|
+
import './chunk-VCEA7333.js';
|
|
8
8
|
import { readConfig, CONFIG_FILE, configPath } from './chunk-P6ATKV2R.js';
|
|
9
|
-
import './chunk-
|
|
9
|
+
import './chunk-AXUYE7HH.js';
|
|
10
10
|
import { readText, git } from './chunk-4VPBP6K6.js';
|
|
11
11
|
import { promises } from 'fs';
|
|
12
12
|
import path from 'path';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
export { deriveFailOn, derivedCommands, planWorkflow, riskReachability } from './chunk-
|
|
3
|
-
import './chunk-
|
|
2
|
+
export { deriveFailOn, derivedCommands, planWorkflow, riskReachability } from './chunk-XYJIDPKZ.js';
|
|
3
|
+
import './chunk-BXBSFDYL.js';
|
|
4
4
|
import './chunk-P6ATKV2R.js';
|
|
5
|
-
import './chunk-
|
|
5
|
+
import './chunk-AXUYE7HH.js';
|
|
6
6
|
import './chunk-4VPBP6K6.js';
|
package/dist/index.d.ts
CHANGED
|
@@ -1127,7 +1127,7 @@ declare const gateIntegrityLens: Lens;
|
|
|
1127
1127
|
* Gaps in the sequence are deliberate: a number stays retired even when the change that took it
|
|
1128
1128
|
* never shipped, so a version cited in a baseline or a record means exactly one pack, ever.
|
|
1129
1129
|
*/
|
|
1130
|
-
declare const PACK_VERSION = "
|
|
1130
|
+
declare const PACK_VERSION = "17";
|
|
1131
1131
|
|
|
1132
1132
|
declare const VERSION: string;
|
|
1133
1133
|
declare const NAME: string;
|
package/dist/index.js
CHANGED
|
@@ -20,7 +20,7 @@ var __export = (target, all) => {
|
|
|
20
20
|
var PACK_VERSION;
|
|
21
21
|
var init_version = __esm({
|
|
22
22
|
"src/pack/version.ts"() {
|
|
23
|
-
PACK_VERSION = "
|
|
23
|
+
PACK_VERSION = "17";
|
|
24
24
|
}
|
|
25
25
|
});
|
|
26
26
|
|
|
@@ -30,7 +30,7 @@ var init_package = __esm({
|
|
|
30
30
|
"package.json"() {
|
|
31
31
|
package_default = {
|
|
32
32
|
name: "etymd",
|
|
33
|
-
version: "0.19.
|
|
33
|
+
version: "0.19.3",
|
|
34
34
|
description: "Keep your agent instructions true \u2014 verify AGENTS.md, CLAUDE.md, rules, skills, and the task you hand an agent against the actual repo, with drift caught over time and a regression ledger.",
|
|
35
35
|
keywords: [
|
|
36
36
|
"cli",
|
|
@@ -501,23 +501,40 @@ async function detectArtifacts(root) {
|
|
|
501
501
|
});
|
|
502
502
|
return artifacts;
|
|
503
503
|
}
|
|
504
|
+
function withoutFencedCode(text) {
|
|
505
|
+
const kept = [];
|
|
506
|
+
let fence = null;
|
|
507
|
+
for (const line of text.split("\n")) {
|
|
508
|
+
if (fence === null) {
|
|
509
|
+
const open = /^ {0,3}(`{3,}|~{3,})(.*)$/.exec(line);
|
|
510
|
+
if (open?.[1] && !(open[1][0] === "`" && open[2]?.includes("`"))) fence = open[1];
|
|
511
|
+
else kept.push(line);
|
|
512
|
+
} else {
|
|
513
|
+
const close = /^ {0,3}(`{3,}|~{3,})[ \t]*$/.exec(line)?.[1];
|
|
514
|
+
if (close && close[0] === fence[0] && close.length >= fence.length) fence = null;
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
return kept.join("\n");
|
|
518
|
+
}
|
|
504
519
|
function detectClaudeCodeVersion() {
|
|
505
520
|
const override = process.env.ETYMD_CLAUDE_VERSION;
|
|
506
521
|
if (override !== void 0) {
|
|
507
522
|
return Promise.resolve(override === "none" || override === "" ? null : override);
|
|
508
523
|
}
|
|
509
|
-
claudeVersionMemo ??= pExecFile2("claude", ["--version"], { timeout: 5e3 }).then(({ stdout }) => /(\d+\.\d+\.\d+)/.exec(stdout)?.[1] ?? null).catch(() => null);
|
|
524
|
+
claudeVersionMemo ??= pExecFile2("claude", ["--version"], { timeout: 5e3 }).then(({ stdout }) => /(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)(?=\s|$)/.exec(stdout)?.[1] ?? null).catch(() => null);
|
|
510
525
|
return claudeVersionMemo;
|
|
511
526
|
}
|
|
512
|
-
function
|
|
513
|
-
const
|
|
514
|
-
const
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
527
|
+
function mayPredate(a, b) {
|
|
528
|
+
const parse = (v) => /^(\d+)\.(\d+)\.(\d+)(-[0-9A-Za-z.-]+)?$/.exec(v.trim());
|
|
529
|
+
const pa = parse(a);
|
|
530
|
+
const pb = parse(b);
|
|
531
|
+
if (!pa || !pb) return true;
|
|
532
|
+
for (let i = 1; i <= 3; i++) {
|
|
533
|
+
const x = Number(pa[i]);
|
|
534
|
+
const y = Number(pb[i]);
|
|
518
535
|
if (x !== y) return x < y;
|
|
519
536
|
}
|
|
520
|
-
return
|
|
537
|
+
return pa[4] !== void 0 && pb[4] === void 0;
|
|
521
538
|
}
|
|
522
539
|
async function checkClaudePointer(root, claudeVersion) {
|
|
523
540
|
const agentsAbs = path2.join(root, "AGENTS.md");
|
|
@@ -535,11 +552,11 @@ async function checkClaudePointer(root, claudeVersion) {
|
|
|
535
552
|
return { ok: true, via: "same-file" };
|
|
536
553
|
}
|
|
537
554
|
const rootClaude = await readText(claudeAbs);
|
|
538
|
-
if (rootClaude !== null && ROOT_POINTER_IMPORT_RE.test(rootClaude)) {
|
|
555
|
+
if (rootClaude !== null && ROOT_POINTER_IMPORT_RE.test(withoutFencedCode(rootClaude))) {
|
|
539
556
|
return { ok: true, via: "root-import" };
|
|
540
557
|
}
|
|
541
558
|
const dotClaude = await readText(path2.join(root, ".claude", "CLAUDE.md"));
|
|
542
|
-
if (dotClaude !== null && DOT_CLAUDE_IMPORT_RE.test(dotClaude)) {
|
|
559
|
+
if (dotClaude !== null && DOT_CLAUDE_IMPORT_RE.test(withoutFencedCode(dotClaude))) {
|
|
543
560
|
return { ok: true, via: "dotclaude-import" };
|
|
544
561
|
}
|
|
545
562
|
if (rootClaude !== null || dotClaude !== null) {
|
|
@@ -550,7 +567,7 @@ async function checkClaudePointer(root, claudeVersion) {
|
|
|
550
567
|
};
|
|
551
568
|
}
|
|
552
569
|
const version = await detectClaudeCodeVersion() ;
|
|
553
|
-
if (version !== null &&
|
|
570
|
+
if (version !== null && mayPredate(version, CLAUDE_AGENTS_FALLBACK_VERSION)) {
|
|
554
571
|
return {
|
|
555
572
|
ok: false,
|
|
556
573
|
kind: "old-reader",
|
|
@@ -779,8 +796,8 @@ var init_detect = __esm({
|
|
|
779
796
|
];
|
|
780
797
|
SHELL_SHEBANG = /^#!\s*\/\S*\b(?:ba|da|z)?sh\b|^#!\s*\/usr\/bin\/env\s+(?:ba|da|z)?sh\b/;
|
|
781
798
|
SHELL_EXT = /\.(?:sh|bash|zsh)$/;
|
|
782
|
-
ROOT_POINTER_IMPORT_RE =
|
|
783
|
-
DOT_CLAUDE_IMPORT_RE =
|
|
799
|
+
ROOT_POINTER_IMPORT_RE = /^ {0,3}@(\.\/)?AGENTS\.md[ \t]*$/m;
|
|
800
|
+
DOT_CLAUDE_IMPORT_RE = /^ {0,3}@\.\.\/AGENTS\.md[ \t]*$/m;
|
|
784
801
|
CLAUDE_AGENTS_FALLBACK_VERSION = "2.1.277";
|
|
785
802
|
pExecFile2 = promisify(execFile);
|
|
786
803
|
}
|
|
@@ -1319,49 +1336,86 @@ exit 0
|
|
|
1319
1336
|
}
|
|
1320
1337
|
function generateShellDiscoveryScript() {
|
|
1321
1338
|
return stampGenerated(`#!/usr/bin/env sh
|
|
1322
|
-
# etymd: shell script discovery for the pre-push shellcheck step.
|
|
1323
|
-
#
|
|
1324
|
-
#
|
|
1325
|
-
#
|
|
1339
|
+
# etymd: shell script discovery for the pre-push shellcheck step. Two calls per commit:
|
|
1340
|
+
# discover-shell-scripts.sh --skips <scratch> <ls-tree record>...
|
|
1341
|
+
# discover-shell-scripts.sh --commit <scratch> <tree> <commit> <commit>:<path>...
|
|
1342
|
+
# The first counts the changed paths that are symlinks or submodule entries. The second reads
|
|
1343
|
+
# the candidates \`git grep\` found with a line starting \`#!\`, and classifies each by its FIRST
|
|
1344
|
+
# line. Verdicts land in the scratch: scripts (NUL-delimited matches) and one dot per decision
|
|
1345
|
+
# into count / zsh-count / skip-count, tallied by the hook after the pipeline. Each script found
|
|
1346
|
+
# is written into <tree> at its path, so the checker reads it there.
|
|
1326
1347
|
#
|
|
1327
|
-
#
|
|
1328
|
-
#
|
|
1329
|
-
#
|
|
1330
|
-
#
|
|
1331
|
-
# that EXISTS but cannot be read is the other branch \u2014 coverage would silently shrink, so it
|
|
1332
|
-
# fails, naming the path.
|
|
1348
|
+
# Every byte comes from git's object store as the raw blob \u2014 \`git cat-file blob\`, which
|
|
1349
|
+
# applies no eol, text or filter attribute. A checkout converts: under \`eol=crlf\` the shebang
|
|
1350
|
+
# line ends in a carriage return, the match below fails, and the script leaves the checked set
|
|
1351
|
+
# without a word. Only the entries handed in are read, never the whole commit.
|
|
1333
1352
|
#
|
|
1334
|
-
#
|
|
1335
|
-
#
|
|
1336
|
-
#
|
|
1337
|
-
#
|
|
1338
|
-
#
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1353
|
+
# A symlink or submodule entry has no script bytes of its own \u2014 a link's target is a tracked
|
|
1354
|
+
# path checked under its own name \u2014 so it is a disclosed skip, never a block. A candidate that
|
|
1355
|
+
# cannot be read fails, naming the path: coverage would otherwise silently shrink.
|
|
1356
|
+
#
|
|
1357
|
+
# The match/error protocol: grep reports "no match" as 1 and a failure as 2 or more, and only
|
|
1358
|
+
# the first is a verdict. Letting a failure fall through would pass a broken matcher as "not a
|
|
1359
|
+
# shell script" \u2014 the exact silent coverage-shrink the fail-closed rules exist to prevent. The
|
|
1360
|
+
# status is captured on the grep's own line (\`|| st=$?\`), so no command added later can come
|
|
1361
|
+
# between the grep and the check and silently replace the status being read.
|
|
1362
|
+
case \${1-} in
|
|
1363
|
+
(--skips)
|
|
1364
|
+
# Records only: a symlink or submodule entry is counted, everything else is left to the
|
|
1365
|
+
# candidate pass. Builtins only \u2014 this loop sees every changed path.
|
|
1366
|
+
work=$2
|
|
1367
|
+
shift 2
|
|
1368
|
+
for record do
|
|
1369
|
+
case \${record%% *} in
|
|
1370
|
+
(100644|100755) ;;
|
|
1371
|
+
(*) printf . >> "$work/skip-count" || exit 1 ;;
|
|
1372
|
+
esac
|
|
1373
|
+
done
|
|
1374
|
+
exit 0 ;;
|
|
1375
|
+
(--commit)
|
|
1376
|
+
[ $# -ge 4 ] || exit 1 ;;
|
|
1377
|
+
(*)
|
|
1378
|
+
echo "etymd: discover-shell-scripts.sh expects --commit or --skips; the pre-push beside it is older than this classifier \u2014 run 'etymd gates'" >&2
|
|
1379
|
+
exit 1 ;;
|
|
1380
|
+
esac
|
|
1381
|
+
work=$2
|
|
1382
|
+
tree=$3
|
|
1383
|
+
sha=$4
|
|
1384
|
+
shift 4
|
|
1385
|
+
for entry do
|
|
1386
|
+
file=\${entry#"$sha":}
|
|
1387
|
+
# 4096 bytes bound the classifying read \u2014 a binary with no newline would otherwise be copied
|
|
1388
|
+
# whole into the next step. The second head restores line-1-only semantics, so a shebang
|
|
1389
|
+
# embedded on a LATER line of a document cannot match the patterns below.
|
|
1390
|
+
git cat-file blob "$sha:$file" > "$work/blob" && head -c 4096 "$work/blob" > "$work/head-bytes" || {
|
|
1350
1391
|
echo "etymd: cannot read tracked file for shellcheck: $file" >&2
|
|
1351
1392
|
exit 1
|
|
1352
1393
|
}
|
|
1353
1394
|
head -n 1 "$work/head-bytes" > "$work/first-line" || exit 1
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1395
|
+
st=0
|
|
1396
|
+
grep -qE "^#!.*[/ ](ba|da)?sh( |$)" "$work/first-line" || st=$?
|
|
1397
|
+
case $st in
|
|
1398
|
+
(0)
|
|
1399
|
+
case $file in
|
|
1400
|
+
(*/*) dir=\${file%/*} ;;
|
|
1401
|
+
(*) dir=. ;;
|
|
1402
|
+
esac
|
|
1403
|
+
mkdir -p -- "$tree/$dir" && mv -- "$work/blob" "$tree/$file" || {
|
|
1404
|
+
echo "etymd: cannot stage tracked file for shellcheck: $file" >&2
|
|
1405
|
+
exit 1
|
|
1406
|
+
}
|
|
1407
|
+
printf "./%s\\0" "$file" >> "$work/scripts" || exit 1
|
|
1408
|
+
printf . >> "$work/count" || exit 1 ;;
|
|
1409
|
+
(1)
|
|
1410
|
+
st=0
|
|
1411
|
+
grep -qE "^#!.*[/ ]zsh( |$)" "$work/first-line" || st=$?
|
|
1412
|
+
case $st in
|
|
1413
|
+
(0) printf . >> "$work/zsh-count" || exit 1 ;;
|
|
1414
|
+
(1) ;;
|
|
1415
|
+
(*) exit 1 ;;
|
|
1416
|
+
esac ;;
|
|
1417
|
+
(*) exit 1 ;;
|
|
1418
|
+
esac
|
|
1365
1419
|
done
|
|
1366
1420
|
`);
|
|
1367
1421
|
}
|
|
@@ -1369,7 +1423,7 @@ function shellcheckStep() {
|
|
|
1369
1423
|
return `
|
|
1370
1424
|
# Shell correctness. Scripts are discovered by shebang over TRACKED files at push time, so a
|
|
1371
1425
|
# script added later is covered without regenerating this hook. The commits BEING PUSHED are
|
|
1372
|
-
# the bytes that ship, so each
|
|
1426
|
+
# the bytes that ship, so each script is read as its raw blob from git's object store \u2014
|
|
1373
1427
|
# never the working tree (wrong in both directions: a fixed tree let an unfixed commit ship, and
|
|
1374
1428
|
# a dirty tree shared by several sessions blocked an unrelated push) and never the tip alone (a
|
|
1375
1429
|
# bad commit under a clean tip shipped while the gate read only the tip's fix). The classifier
|
|
@@ -1431,37 +1485,87 @@ if command -v shellcheck >/dev/null 2>&1; then
|
|
|
1431
1485
|
exit 1
|
|
1432
1486
|
}
|
|
1433
1487
|
shas=$(sort -u "$shellcheck_tmp/shas") || exit 1
|
|
1434
|
-
[ -n "$shas" ]
|
|
1435
|
-
|
|
1488
|
+
if [ -n "$shas" ]; then
|
|
1489
|
+
# Said up front: a first push from a fresh clone can carry the whole history, and a long
|
|
1490
|
+
# silent hook reads as a hung one.
|
|
1491
|
+
echo "\u203A shellcheck: $(printf '%s\\n' "$shas" | wc -l | tr -d ' ') commit(s) in the pushed range"
|
|
1492
|
+
else
|
|
1493
|
+
echo "\u203A shellcheck: no commit in the pushed refs (deletes only, or nothing on stdin) \u2014 nothing to check"
|
|
1494
|
+
fi
|
|
1495
|
+
# Resolved once, before any cd: the checker runs inside each commit's scratch tree, and a
|
|
1436
1496
|
# relative hook path would no longer point at it from there.
|
|
1437
1497
|
discover="$(cd "$(dirname "$0")" && pwd)/discover-shell-scripts.sh" || exit 1
|
|
1438
1498
|
for sha in $shas; do
|
|
1439
|
-
# A fresh directory per commit
|
|
1440
|
-
#
|
|
1441
|
-
# every early exit.
|
|
1499
|
+
# A fresh directory per commit holding only that commit's scripts, removed once it is
|
|
1500
|
+
# checked; the subshell trap above still cleans the root on every early exit.
|
|
1442
1501
|
tree=$(mktemp -d "$shellcheck_tmp/commit.XXXXXX") || exit 1
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
# the checked set without a word. read-tree + checkout-index writes every tracked blob.
|
|
1446
|
-
# Large-file pointers stay pointers \u2014 they are never shell, and a push must not need the
|
|
1447
|
-
# network to be checked.
|
|
1448
|
-
if ! git cat-file -e "\${sha}^{commit}" 2>/dev/null \\
|
|
1449
|
-
|| ! GIT_INDEX_FILE="$shellcheck_tmp/index" git read-tree "$sha" 2>/dev/null \\
|
|
1450
|
-
|| ! GIT_INDEX_FILE="$shellcheck_tmp/index" GIT_LFS_SKIP_SMUDGE=1 \\
|
|
1451
|
-
git checkout-index -a -f --prefix="$tree/" 2>/dev/null; then
|
|
1452
|
-
echo "\u2717 shellcheck: could not materialise $(git rev-parse --short "$sha" 2>/dev/null || echo "$sha") \u2014 refusing the push rather than certifying bytes this gate did not read" >&2
|
|
1453
|
-
exit 1
|
|
1454
|
-
fi
|
|
1455
|
-
if [ -n "$(git ls-tree -r --name-only "$sha")" ] && [ -z "$(ls -A "$tree")" ]; then
|
|
1456
|
-
echo "\u2717 shellcheck: $(git rev-parse --short "$sha" 2>/dev/null || echo "$sha") extracted to an empty tree \u2014 refusing the push" >&2
|
|
1502
|
+
if ! git cat-file -e "\${sha}^{commit}" 2>/dev/null; then
|
|
1503
|
+
echo "\u2717 shellcheck: cannot read commit $(git rev-parse --short "$sha" 2>/dev/null || echo "$sha") \u2014 refusing the push rather than certifying bytes this gate did not read" >&2
|
|
1457
1504
|
exit 1
|
|
1458
1505
|
fi
|
|
1459
1506
|
: > "$shellcheck_tmp/scripts" && : > "$shellcheck_tmp/count" && : > "$shellcheck_tmp/zsh-count" && : > "$shellcheck_tmp/skip-count" || exit 1
|
|
1460
|
-
|
|
1461
|
-
|
|
1507
|
+
# Only the paths this commit adds, copies, modifies, renames or retypes against its first
|
|
1508
|
+
# parent. A script the commit did not touch has its parent's bytes, and that parent was
|
|
1509
|
+
# either gated when it reached the remote or sits in this range and is checked here; a
|
|
1510
|
+
# merge diffed against its first parent brings in everything the other side added.
|
|
1511
|
+
# Checking every script in every commit multiplied the cost by the range length, so a long
|
|
1512
|
+
# push of a script-heavy repo ran long enough to look hung. Two changes alter the verdict
|
|
1513
|
+
# on bytes nobody touched \u2014 the classifier deciding what is a script, and a .shellcheckrc
|
|
1514
|
+
# deciding what is a finding \u2014 so a commit that touches either, deletion included (a
|
|
1515
|
+
# removed .shellcheckrc re-enables every check it disabled), is checked whole. So is a
|
|
1516
|
+
# commit that adds or changes this hook: a repo adopting the gate after the fact has
|
|
1517
|
+
# scripts no gate ever read, and the commit installing it is where they get read once.
|
|
1518
|
+
short=$(git rev-parse --short "$sha" 2>/dev/null || echo "$sha")
|
|
1519
|
+
if git rev-parse -q --verify "\${sha}^1^{commit}" >/dev/null 2>&1; then
|
|
1520
|
+
git diff-tree -r -z --name-only --no-commit-id "\${sha}^1" "$sha" > "$shellcheck_tmp/touched" \\
|
|
1521
|
+
&& git diff-tree -r -z --name-only --no-commit-id --diff-filter=ACMRT "\${sha}^1" "$sha" > "$shellcheck_tmp/tracked"
|
|
1522
|
+
else
|
|
1523
|
+
git diff-tree -r -z --name-only --no-commit-id --root "$sha" > "$shellcheck_tmp/touched" \\
|
|
1524
|
+
&& cp "$shellcheck_tmp/touched" "$shellcheck_tmp/tracked"
|
|
1525
|
+
fi || {
|
|
1526
|
+
echo "etymd: cannot enumerate the paths $short changes for shellcheck" >&2
|
|
1462
1527
|
exit 1
|
|
1463
1528
|
}
|
|
1464
|
-
|
|
1529
|
+
where="changed in $short"
|
|
1530
|
+
# Through a file, not a pipe: a pipeline reports only grep's status, and a failing tr would
|
|
1531
|
+
# hand grep nothing \u2014 a "no match" that silently takes the narrow path.
|
|
1532
|
+
tr '\\000' '\\n' < "$shellcheck_tmp/touched" > "$shellcheck_tmp/touched-lines" || exit 1
|
|
1533
|
+
# grep: 1 is "no match", a verdict; anything higher is the matcher failing. The status is
|
|
1534
|
+
# taken on the grep's own line so nothing added later can come between them.
|
|
1535
|
+
st=0
|
|
1536
|
+
grep -qE '(^|/)(\\.shellcheckrc|discover-shell-scripts\\.sh|pre-push)$' "$shellcheck_tmp/touched-lines" || st=$?
|
|
1537
|
+
case $st in
|
|
1538
|
+
(0)
|
|
1539
|
+
where="in $short (whole tree: the gate, its classifier or a .shellcheckrc changed)"
|
|
1540
|
+
git ls-tree -r -z --name-only "$sha" > "$shellcheck_tmp/tracked" || {
|
|
1541
|
+
echo "etymd: cannot enumerate the tree of $short for shellcheck" >&2
|
|
1542
|
+
exit 1
|
|
1543
|
+
} ;;
|
|
1544
|
+
(1) ;;
|
|
1545
|
+
(*)
|
|
1546
|
+
echo "etymd: shell script discovery failed; cannot tell whether $short changes the gate, its classifier or a .shellcheckrc" >&2
|
|
1547
|
+
exit 1 ;;
|
|
1548
|
+
esac
|
|
1549
|
+
# Only these paths are read, each as its raw blob \u2014 never a checkout of the commit, which
|
|
1550
|
+
# would apply eol and filter attributes (a CRLF shebang left the checked set silently) and
|
|
1551
|
+
# write the whole tree to check a handful of files. Never \`git archive\` either: it honours
|
|
1552
|
+
# export-ignore, so a script so marked would leave the set without a word.
|
|
1553
|
+
: > "$shellcheck_tmp/records" && : > "$shellcheck_tmp/candidates" || exit 1
|
|
1554
|
+
if [ -s "$shellcheck_tmp/tracked" ]; then
|
|
1555
|
+
# The candidates: changed blobs holding a line that starts \`#!\`, found in one pass over
|
|
1556
|
+
# the raw blobs (git grep reads no eol or filter conversion). Only these are read one by
|
|
1557
|
+
# one \u2014 a file with no such line cannot have a shebang first line. grep's "no match" is
|
|
1558
|
+
# 1, a verdict; xargs would fold it into a failure, so the wrapper maps 1 to 0 and lets
|
|
1559
|
+
# anything higher through as the failure it is.
|
|
1560
|
+
xargs -0 git --literal-pathspecs ls-tree -z "$sha" -- < "$shellcheck_tmp/tracked" > "$shellcheck_tmp/records" \\
|
|
1561
|
+
&& xargs -0 sh -c 'git --literal-pathspecs grep -z -l --full-name -e "^#!" "$0" -- "$@"; st=$?; [ "$st" -le 1 ]' "$sha" \\
|
|
1562
|
+
< "$shellcheck_tmp/tracked" > "$shellcheck_tmp/candidates" || {
|
|
1563
|
+
echo "etymd: shell script discovery failed; cannot list the tracked entries of $short" >&2
|
|
1564
|
+
exit 1
|
|
1565
|
+
}
|
|
1566
|
+
fi
|
|
1567
|
+
xargs -0 "$discover" --skips "$shellcheck_tmp" < "$shellcheck_tmp/records" \\
|
|
1568
|
+
&& xargs -0 "$discover" --commit "$shellcheck_tmp" "$tree" "$sha" < "$shellcheck_tmp/candidates" || {
|
|
1465
1569
|
echo "etymd: shell script discovery failed; shellcheck coverage is incomplete" >&2
|
|
1466
1570
|
exit 1
|
|
1467
1571
|
}
|
|
@@ -1469,13 +1573,15 @@ if command -v shellcheck >/dev/null 2>&1; then
|
|
|
1469
1573
|
zsh_count=$(wc -c < "$shellcheck_tmp/zsh-count") || exit 1
|
|
1470
1574
|
skip_count=$(wc -c < "$shellcheck_tmp/skip-count") || exit 1
|
|
1471
1575
|
if [ "$skip_count" -gt 0 ]; then
|
|
1472
|
-
echo "\u203A shellcheck: $((skip_count)) tracked path(s)
|
|
1576
|
+
echo "\u203A shellcheck: $((skip_count)) tracked path(s) that are symlinks or submodule entries \u2014 no script bytes of their own (a link's target is checked under its own path); not checked, not failed"
|
|
1473
1577
|
fi
|
|
1474
1578
|
if [ "$zsh_count" -gt 0 ]; then
|
|
1475
1579
|
echo "\u203A shellcheck: $((zsh_count)) zsh script(s) excluded \u2014 shellcheck cannot parse zsh (SC1071); not checked, not failed"
|
|
1476
1580
|
fi
|
|
1477
|
-
if [ "$count" -
|
|
1478
|
-
echo "\u203A shellcheck
|
|
1581
|
+
if [ "$count" -eq 0 ]; then
|
|
1582
|
+
echo "\u203A shellcheck: no shell script $where \u2014 nothing to check there"
|
|
1583
|
+
else
|
|
1584
|
+
echo "\u203A shellcheck ($((count)) scripts $where, blocking at severity=warning)"
|
|
1479
1585
|
( cd "$tree" && xargs -0 shellcheck -S warning -- < "$shellcheck_tmp/scripts" ) || {
|
|
1480
1586
|
echo " fix, or justify inline with '# shellcheck disable=SCxxxx # why'"
|
|
1481
1587
|
exit 1
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { applyFiles } from './chunk-GWGKEPRX.js';
|
|
3
|
-
import { planWorkflow } from './chunk-
|
|
4
|
-
import './chunk-
|
|
3
|
+
import { planWorkflow } from './chunk-XYJIDPKZ.js';
|
|
4
|
+
import './chunk-BXBSFDYL.js';
|
|
5
5
|
import { theme, renderFacts, print, renderPlan, section, glyph } from './chunk-HI7NWPRA.js';
|
|
6
|
-
import { scanProject } from './chunk-
|
|
7
|
-
import { VERSION } from './chunk-
|
|
6
|
+
import { scanProject, checkClaudePointer } from './chunk-VVUEOA4V.js';
|
|
7
|
+
import { VERSION } from './chunk-VCEA7333.js';
|
|
8
8
|
import { writeCachedFacts, deriveProfile, writeBaseline, CACHE_DIR } from './chunk-P6ATKV2R.js';
|
|
9
|
-
import { PACK_VERSION } from './chunk-
|
|
9
|
+
import { PACK_VERSION } from './chunk-AXUYE7HH.js';
|
|
10
10
|
import { git, readText } from './chunk-4VPBP6K6.js';
|
|
11
11
|
import { promises } from 'fs';
|
|
12
12
|
import path from 'path';
|
|
@@ -102,6 +102,13 @@ async function run(opts) {
|
|
|
102
102
|
section("Onboarded");
|
|
103
103
|
for (const w of result.written) print(` ${glyph.ok} ${theme.dim("wrote")} ${theme.info(w)}`);
|
|
104
104
|
for (const sk of result.skipped) print(` ${glyph.bullet} ${theme.dim("kept")} ${theme.dim(sk)}`);
|
|
105
|
+
const pointer = await checkClaudePointer(opts.cwd);
|
|
106
|
+
if (!pointer.ok && pointer.kind === "no-import") {
|
|
107
|
+
const line = pointer.detail.includes(".claude/CLAUDE.md") ? "@../AGENTS.md" : "@AGENTS.md";
|
|
108
|
+
print(
|
|
109
|
+
` ${glyph.partial} ${theme.warn(pointer.detail)} ${theme.dim(`\u2014 Claude Code reads that file instead; add a full-line ${line} import`)}`
|
|
110
|
+
);
|
|
111
|
+
}
|
|
105
112
|
print(
|
|
106
113
|
` ${glyph.ok} ${theme.dim("baseline approved \u2192")} ${theme.info(".etymd/baseline.json")} ${theme.dim("(commit it \u2014 drift is measured against it)")}`
|
|
107
114
|
);
|