@smeltjs/core 0.3.0 → 0.4.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/dist/agents/guide.d.ts +68 -0
- package/dist/agents/guide.d.ts.map +1 -0
- package/dist/agents/guide.js +72 -0
- package/dist/agents/guide.js.map +1 -0
- package/dist/agents/instructions.d.ts +133 -0
- package/dist/agents/instructions.d.ts.map +1 -0
- package/dist/agents/instructions.js +212 -0
- package/dist/agents/instructions.js.map +1 -0
- package/dist/agents/lint.d.ts +154 -0
- package/dist/agents/lint.d.ts.map +1 -0
- package/dist/agents/lint.js +761 -0
- package/dist/agents/lint.js.map +1 -0
- package/dist/agents/split.d.ts +113 -0
- package/dist/agents/split.d.ts.map +1 -0
- package/dist/agents/split.js +234 -0
- package/dist/agents/split.js.map +1 -0
- package/dist/cli/agents.d.ts +39 -0
- package/dist/cli/agents.d.ts.map +1 -0
- package/dist/cli/agents.js +121 -0
- package/dist/cli/agents.js.map +1 -0
- package/dist/cli/args.d.ts +1 -0
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/config.d.ts +20 -0
- package/dist/cli/config.d.ts.map +1 -1
- package/dist/cli/config.js +34 -1
- package/dist/cli/config.js.map +1 -1
- package/dist/cli/hooks.d.ts +25 -16
- package/dist/cli/hooks.d.ts.map +1 -1
- package/dist/cli/hooks.js +71 -178
- package/dist/cli/hooks.js.map +1 -1
- package/dist/cli/report.d.ts +23 -0
- package/dist/cli/report.d.ts.map +1 -1
- package/dist/cli/report.js +140 -2
- package/dist/cli/report.js.map +1 -1
- package/dist/cli/run.d.ts +5 -3
- package/dist/cli/run.d.ts.map +1 -1
- package/dist/cli/run.js +2 -1
- package/dist/cli/run.js.map +1 -1
- package/dist/cli/subcommands/agents.d.ts +72 -0
- package/dist/cli/subcommands/agents.d.ts.map +1 -0
- package/dist/cli/subcommands/agents.js +162 -0
- package/dist/cli/subcommands/agents.js.map +1 -0
- package/dist/cli/subcommands/flags.d.ts +3 -0
- package/dist/cli/subcommands/flags.d.ts.map +1 -1
- package/dist/cli/subcommands/flags.js +13 -1
- package/dist/cli/subcommands/flags.js.map +1 -1
- package/dist/cli/subcommands/hooks.d.ts.map +1 -1
- package/dist/cli/subcommands/hooks.js +19 -3
- package/dist/cli/subcommands/hooks.js.map +1 -1
- package/dist/cli/subcommands/registry.d.ts +2 -1
- package/dist/cli/subcommands/registry.d.ts.map +1 -1
- package/dist/cli/subcommands/registry.js +3 -0
- package/dist/cli/subcommands/registry.js.map +1 -1
- package/dist/cli/subcommands/subcommand.d.ts +1 -1
- package/dist/cli/subcommands/subcommand.d.ts.map +1 -1
- package/dist/cli/subcommands/subcommand.js.map +1 -1
- package/dist/harness/codex.d.ts.map +1 -1
- package/dist/harness/codex.js +1 -0
- package/dist/harness/codex.js.map +1 -1
- package/dist/harness/gemini.d.ts.map +1 -1
- package/dist/harness/gemini.js +1 -0
- package/dist/harness/gemini.js.map +1 -1
- package/dist/harness/grok.d.ts.map +1 -1
- package/dist/harness/grok.js +1 -0
- package/dist/harness/grok.js.map +1 -1
- package/dist/harness/hermes.d.ts.map +1 -1
- package/dist/harness/hermes.js +1 -0
- package/dist/harness/hermes.js.map +1 -1
- package/dist/harness/profile.d.ts +26 -1
- package/dist/harness/profile.d.ts.map +1 -1
- package/dist/harness/profile.js +15 -0
- package/dist/harness/profile.js.map +1 -1
- package/dist/harness/registry.d.ts +55 -6
- package/dist/harness/registry.d.ts.map +1 -1
- package/dist/harness/registry.js +63 -6
- package/dist/harness/registry.js.map +1 -1
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -1
- package/dist/text/json-edit.d.ts +71 -0
- package/dist/text/json-edit.d.ts.map +1 -0
- package/dist/text/json-edit.js +219 -0
- package/dist/text/json-edit.js.map +1 -0
- package/dist/types.d.ts +0 -11
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
package/dist/cli/report.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"report.js","sourceRoot":"","sources":["../../src/cli/report.ts"],"names":[],"mappings":"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","sourcesContent":["import type { RepoMap } from '../repomap/map.ts';\nimport type { SmeltResult } from '../types.ts';\n\nimport { CLI_NAME } from './shell.ts';\n\nexport interface ReportInput {\n readonly result: SmeltResult;\n /** What to call the input in the header: a path, or `'<stdin>'`. */\n readonly source: string;\n /** The budget the caller asked for, so the report can say when it was missed. */\n readonly budgetBytes: number;\n /** The exact text that was smelted. Used only to count lines inside each range. */\n readonly inputText: string;\n}\n\n/** Longest explanation printed in full before it gets an ellipsis. */\nconst EXPLANATION_WIDTH = 46;\n\n/**\n * The report, for stderr.\n *\n * Every total here is read straight off the {@link SmeltResult} — `inputBytes`,\n * `outputBytes`, `elisions.length`. The CLI keeps no counters of its own, because two\n * pieces of code counting the same bytes is how a report ends up disagreeing with the\n * library it is reporting on, and the report is the thing a human believes.\n * `test/cli.test.ts` asserts the printed numbers equal the result's fields.\n */\nexport function formatReport({ result, source, budgetBytes, inputText }: ReportInput): string {\n const lines: string[] = [];\n\n lines.push([CLI_NAME, source, result.language, result.planner].join(' '));\n lines.push(\n `in ${group(result.inputBytes)} B → out ${group(result.outputBytes)} B ` +\n `(${delta(result.inputBytes, result.outputBytes)}, ${count(result.elisions.length, 'elision')})`,\n );\n\n if (result.measured !== undefined) {\n const { input, output, unit, measure } = result.measured;\n lines.push(`in ${group(input)} → out ${group(output)} ${unit} (${measure})`);\n }\n\n if (result.outputBytes > budgetBytes) {\n lines.push('');\n lines.push(\n `OVER BUDGET ${group(result.outputBytes)} B against a ${group(budgetBytes)} B budget ` +\n `— over by ${group(result.outputBytes - budgetBytes)} B.`,\n );\n lines.push(' The plan is reported as it came back. smelt did not cut the regions');\n lines.push(' you asked to keep in order to make a number look right.');\n }\n\n if (result.elisions.length === 0) {\n lines.push('');\n lines.push(' nothing elided — the input already fits, or every run was too small to');\n lines.push(' be worth a marker (a marker that costs more than the lines it replaces');\n lines.push(' makes the output bigger).');\n return `${lines.join('\\n')}\\n`;\n }\n\n const rows = result.elisions.map((elision) => ({\n rule: elision.reason.rule,\n lines: String(lineSpan(inputText, elision.range.start, elision.range.end)),\n bytes: group(elision.bytes),\n hash: elision.hash,\n explanation: clip(elision.reason.explanation, EXPLANATION_WIDTH),\n }));\n\n const ruleWidth = width(\n 'rule',\n rows.map((row) => row.rule),\n );\n const linesWidth = width(\n 'lines',\n rows.map((row) => row.lines),\n );\n const bytesWidth = width(\n 'bytes',\n rows.map((row) => row.bytes),\n );\n const hashWidth = width(\n 'hash',\n rows.map((row) => row.hash),\n );\n\n lines.push('');\n lines.push(\n ` ${'rule'.padEnd(ruleWidth)} ${'lines'.padStart(linesWidth)} ` +\n `${'bytes'.padStart(bytesWidth)} ${'hash'.padEnd(hashWidth)} explanation`,\n );\n for (const row of rows) {\n lines.push(\n ` ${row.rule.padEnd(ruleWidth)} ${row.lines.padStart(linesWidth)} ` +\n `${row.bytes.padStart(bytesWidth)} ${row.hash.padEnd(hashWidth)} ${row.explanation}`,\n );\n }\n\n return `${lines.join('\\n')}\\n`;\n}\n\n/** What `smelt map` prints to stderr. */\nexport interface MapReportInput {\n readonly map: RepoMap;\n /** The directory named on the command line, exactly as the user wrote it. */\n readonly source: string;\n /**\n * Where the budget came from — the `ResolvedMapRun.budgetSource` receipt, printed\n * beside the budget so a surprising number can be traced to the flag or the config\n * file that set it without re-deriving the precedence by hand.\n */\n readonly budgetSource: 'flag' | 'config';\n}\n\n/**\n * The map report, for stderr — same law as {@link formatReport}: every number is\n * read straight off the {@link RepoMap} the library returned. In particular the\n * \"bytes used\" figure is `map.outputBytes`, which the library measured off the\n * rendered text — the CLI counts nothing itself, because a report that keeps its\n * own tally is a report that can disagree with the map it describes.\n * `test/guards/repo-map.test.ts` asserts the printed figure equals the actual byte\n * length of what landed on stdout, and a mutation proves the assertion can go red.\n */\nexport function formatMapReport({ map, source, budgetSource }: MapReportInput): string {\n const lines: string[] = [];\n\n lines.push([`${CLI_NAME} map`, source, map.id].join(' '));\n lines.push(\n `files scanned ${group(map.filesScanned)}` +\n (map.binarySkipped === 0 ? '' : ` (${count(map.binarySkipped, 'binary file')} skipped)`) +\n ` symbols ranked ${group(map.definitionsTotal)}`,\n );\n lines.push(\n `included ${group(map.entries.length)} of ${group(map.definitionsTotal)} symbols` +\n (map.pathOnlyTotal === 0\n ? ''\n : ` + ${group(map.pathOnly.length)} of ${group(map.pathOnlyTotal)} path-only files`),\n );\n lines.push(\n `bytes used ${group(map.outputBytes)} of ${group(map.budgetBytes)} budget (${budgetSource}) ` +\n `— the map fits itself to the budget by construction, so there is no over-budget exit`,\n );\n\n if (map.cache !== undefined) {\n lines.push(\n `cache ${count(map.cache.hits, 'hit')}, ${count(map.cache.misses, 'miss', 'es')}, ` +\n `${group(map.cache.discarded)} discarded, ${group(map.cache.pruned)} pruned`,\n );\n }\n for (const warning of map.warnings) {\n lines.push(`warning ${warning.rule}: ${warning.explanation}`);\n }\n\n return `${lines.join('\\n')}\\n`;\n}\n\n/** How many lines a byte range covers in the input. Derived, never tallied separately. */\nfunction lineSpan(text: string, start: number, end: number): number {\n const slice = Buffer.from(text, 'utf8').subarray(start, end).toString('utf8');\n return slice.split('\\n').length;\n}\n\nfunction width(header: string, values: readonly string[]): number {\n return values.reduce((widest, value) => Math.max(widest, value.length), header.length);\n}\n\n/** Thousands separators without pulling in ICU, so the output is identical everywhere. */\nfunction group(n: number): string {\n const digits = String(Math.abs(Math.trunc(n)));\n const grouped = digits.replace(/\\B(?=(\\d{3})+(?!\\d))/g, ',');\n return n < 0 ? `-${grouped}` : grouped;\n}\n\nfunction delta(inputBytes: number, outputBytes: number): string {\n if (inputBytes === 0) return 'empty input';\n const percent = ((outputBytes - inputBytes) / inputBytes) * 100;\n const sign = percent > 0 ? '+' : '';\n return `${sign}${percent.toFixed(1)}%`;\n}\n\nfunction count(n: number, noun: string, pluralSuffix = 's'): string {\n return `${group(n)} ${noun}${n === 1 ? '' : pluralSuffix}`;\n}\n\nfunction clip(text: string, max: number): string {\n return text.length <= max ? text : `${text.slice(0, max - 1)}…`;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"report.js","sourceRoot":"","sources":["../../src/cli/report.ts"],"names":[],"mappings":"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","sourcesContent":["import { GUIDE, GUIDE_TITLE } from '../agents/guide.ts';\nimport { overBudgetBytes } from '../agents/lint.ts';\nimport type { AgentsLintReport, AgentsMirrorReport } from '../agents/lint.ts';\nimport type { RepoMap } from '../repomap/map.ts';\nimport type { SmeltResult } from '../types.ts';\n\nimport { CONFIG_FILE_NAME } from './config.ts';\nimport { CLI_NAME } from './shell.ts';\n\nexport interface ReportInput {\n readonly result: SmeltResult;\n /** What to call the input in the header: a path, or `'<stdin>'`. */\n readonly source: string;\n /** The budget the caller asked for, so the report can say when it was missed. */\n readonly budgetBytes: number;\n /** The exact text that was smelted. Used only to count lines inside each range. */\n readonly inputText: string;\n}\n\n/** Longest explanation printed in full before it gets an ellipsis. */\nconst EXPLANATION_WIDTH = 46;\n\n/**\n * The report, for stderr.\n *\n * Every total here is read straight off the {@link SmeltResult} — `inputBytes`,\n * `outputBytes`, `elisions.length`. The CLI keeps no counters of its own, because two\n * pieces of code counting the same bytes is how a report ends up disagreeing with the\n * library it is reporting on, and the report is the thing a human believes.\n * `test/cli.test.ts` asserts the printed numbers equal the result's fields.\n */\nexport function formatReport({ result, source, budgetBytes, inputText }: ReportInput): string {\n const lines: string[] = [];\n\n lines.push([CLI_NAME, source, result.language, result.planner].join(' '));\n lines.push(\n `in ${group(result.inputBytes)} B → out ${group(result.outputBytes)} B ` +\n `(${delta(result.inputBytes, result.outputBytes)}, ${count(result.elisions.length, 'elision')})`,\n );\n\n if (result.measured !== undefined) {\n const { input, output, unit, measure } = result.measured;\n lines.push(`in ${group(input)} → out ${group(output)} ${unit} (${measure})`);\n }\n\n if (result.outputBytes > budgetBytes) {\n lines.push('');\n lines.push(\n `OVER BUDGET ${group(result.outputBytes)} B against a ${group(budgetBytes)} B budget ` +\n `— over by ${group(result.outputBytes - budgetBytes)} B.`,\n );\n lines.push(' The plan is reported as it came back. smelt did not cut the regions');\n lines.push(' you asked to keep in order to make a number look right.');\n }\n\n if (result.elisions.length === 0) {\n lines.push('');\n lines.push(' nothing elided — the input already fits, or every run was too small to');\n lines.push(' be worth a marker (a marker that costs more than the lines it replaces');\n lines.push(' makes the output bigger).');\n return `${lines.join('\\n')}\\n`;\n }\n\n const rows = result.elisions.map((elision) => ({\n rule: elision.reason.rule,\n lines: String(lineSpan(inputText, elision.range.start, elision.range.end)),\n bytes: group(elision.bytes),\n hash: elision.hash,\n explanation: clip(elision.reason.explanation, EXPLANATION_WIDTH),\n }));\n\n const ruleWidth = width(\n 'rule',\n rows.map((row) => row.rule),\n );\n const linesWidth = width(\n 'lines',\n rows.map((row) => row.lines),\n );\n const bytesWidth = width(\n 'bytes',\n rows.map((row) => row.bytes),\n );\n const hashWidth = width(\n 'hash',\n rows.map((row) => row.hash),\n );\n\n lines.push('');\n lines.push(\n ` ${'rule'.padEnd(ruleWidth)} ${'lines'.padStart(linesWidth)} ` +\n `${'bytes'.padStart(bytesWidth)} ${'hash'.padEnd(hashWidth)} explanation`,\n );\n for (const row of rows) {\n lines.push(\n ` ${row.rule.padEnd(ruleWidth)} ${row.lines.padStart(linesWidth)} ` +\n `${row.bytes.padStart(bytesWidth)} ${row.hash.padEnd(hashWidth)} ${row.explanation}`,\n );\n }\n\n return `${lines.join('\\n')}\\n`;\n}\n\n/** What `smelt map` prints to stderr. */\nexport interface MapReportInput {\n readonly map: RepoMap;\n /** The directory named on the command line, exactly as the user wrote it. */\n readonly source: string;\n /**\n * Where the budget came from — the `ResolvedMapRun.budgetSource` receipt, printed\n * beside the budget so a surprising number can be traced to the flag or the config\n * file that set it without re-deriving the precedence by hand.\n */\n readonly budgetSource: 'flag' | 'config';\n}\n\n/**\n * The map report, for stderr — same law as {@link formatReport}: every number is\n * read straight off the {@link RepoMap} the library returned. In particular the\n * \"bytes used\" figure is `map.outputBytes`, which the library measured off the\n * rendered text — the CLI counts nothing itself, because a report that keeps its\n * own tally is a report that can disagree with the map it describes.\n * `test/guards/repo-map.test.ts` asserts the printed figure equals the actual byte\n * length of what landed on stdout, and a mutation proves the assertion can go red.\n */\nexport function formatMapReport({ map, source, budgetSource }: MapReportInput): string {\n const lines: string[] = [];\n\n lines.push([`${CLI_NAME} map`, source, map.id].join(' '));\n lines.push(\n `files scanned ${group(map.filesScanned)}` +\n (map.binarySkipped === 0 ? '' : ` (${count(map.binarySkipped, 'binary file')} skipped)`) +\n ` symbols ranked ${group(map.definitionsTotal)}`,\n );\n lines.push(\n `included ${group(map.entries.length)} of ${group(map.definitionsTotal)} symbols` +\n (map.pathOnlyTotal === 0\n ? ''\n : ` + ${group(map.pathOnly.length)} of ${group(map.pathOnlyTotal)} path-only files`),\n );\n lines.push(\n `bytes used ${group(map.outputBytes)} of ${group(map.budgetBytes)} budget (${budgetSource}) ` +\n `— the map fits itself to the budget by construction, so there is no over-budget exit`,\n );\n\n if (map.cache !== undefined) {\n lines.push(\n `cache ${count(map.cache.hits, 'hit')}, ${count(map.cache.misses, 'miss', 'es')}, ` +\n `${group(map.cache.discarded)} discarded, ${group(map.cache.pruned)} pruned`,\n );\n }\n for (const warning of map.warnings) {\n lines.push(`warning ${warning.rule}: ${warning.explanation}`);\n }\n\n return `${lines.join('\\n')}\\n`;\n}\n\n/** What `smelt agents lint` prints. */\nexport interface AgentsReportInput {\n /** The directory named on the command line, exactly as the user wrote it. */\n readonly source: string;\n /** Whether `--strict` was given — it changes what the closing line promises. */\n readonly strict: boolean;\n}\n\n/**\n * The lint report — and unlike the other two, it goes to **stdout**, because here the\n * report *is* the output. `smelt` and `smelt map` put a payload on stdout and their\n * report on stderr so the two can be piped apart; a lint has no payload to separate\n * from, and sending its only output to stderr would make `smelt agents lint > audit.txt`\n * write an empty file.\n *\n * Same law as the other two, though: every number is read off the\n * {@link AgentsLintReport} the library returned. The renderer counts nothing, so it\n * cannot disagree with what was measured — and in particular both totals are the ones\n * the lint computed over the levels, not a second tally over the printed rows. The\n * guide's cited figure is likewise read from `agents/guide.ts` rather than retyped\n * under the guide's name.\n */\nexport function formatAgentsReport(\n report: AgentsLintReport,\n { source, strict }: AgentsReportInput,\n): string {\n const lines: string[] = [`${CLI_NAME} agents lint ${source}`];\n\n if (report.levels.length === 0) {\n lines.push('');\n lines.push(` no AGENTS.md, CLAUDE.md or GEMINI.md under ${source} — nothing is loaded on`);\n lines.push(' every request, so there is nothing to measure. That is a fine state, not a');\n lines.push(` failure; \\`${CLI_NAME} agents lint\\` has no opinion about whether you want one.`);\n return `${lines.join('\\n')}\\n`;\n }\n\n lines.push('');\n lines.push(' the instruction files in this tree');\n const labelWidth = report.levels.reduce(\n (widest, level) =>\n Math.max(widest, level.path.length, ...level.mirrors.map((m) => m.path.length)),\n Math.max(IMPERATIVES_LABEL.length, PER_REQUEST_LABEL.length, WHOLE_TREE_LABEL.length),\n );\n for (const level of report.levels) {\n lines.push(` ${level.path.padEnd(labelWidth)} ${group(level.bytes).padStart(9)} B`);\n for (const mirror of level.mirrors) {\n lines.push(\n ` ${mirror.path.padEnd(labelWidth)} ${' '.repeat(11)}${mirrorNote(mirror.standing)}`,\n );\n }\n }\n lines.push(\n ` ${PER_REQUEST_LABEL.padEnd(labelWidth)} ${group(report.perRequestBytes).padStart(9)} B`,\n );\n // Printed only when it is a different number — which is exactly when a reader could\n // otherwise mistake the sum for a per-request cost. In a single-chain repository the\n // two are equal and a second row would be noise claiming to be information.\n if (report.totalBytes !== report.perRequestBytes) {\n lines.push(\n ` ${WHOLE_TREE_LABEL.padEnd(labelWidth)} ${group(report.totalBytes).padStart(9)} B`,\n );\n lines.push(' a nested file merges with its ancestors, never with its siblings, so no');\n lines.push(' one request loads the whole tree. Per request is the heaviest chain.');\n }\n lines.push(\n ` ${IMPERATIVES_LABEL.padEnd(labelWidth)} ` +\n `${group(report.imperatives.length).padStart(9)}`,\n );\n // The figure is the guide's, so it is read from where the guide is quoted rather\n // than retyped here: an explanation that paraphrases its source drifts from it\n // silently, and this one is printed under the guide's own name.\n for (const wrapped of wrap(`${GUIDE_TITLE} cites: \"${GUIDE.instructionCeiling}\".`, 68)) {\n lines.push(` ${wrapped}`);\n }\n lines.push(' Printed as a citation, compared to nothing: the only ceiling here is');\n lines.push(' the one you set.');\n\n const over = overBudgetBytes(report);\n lines.push('');\n if (report.budgetBytes === undefined) {\n lines.push(` no budget set — add {\"agents\":{\"budgetBytes\":N}} to ${CONFIG_FILE_NAME} and`);\n lines.push(` exceeding it exits 1. ${CLI_NAME} will never invent that number for you.`);\n } else if (over === undefined) {\n lines.push(\n ` within budget ${group(report.totalBytes)} B of ${group(report.budgetBytes)} B ` +\n `(${CONFIG_FILE_NAME}: agents.budgetBytes, against the whole tree).`,\n );\n } else {\n lines.push(\n ` OVER BUDGET ${group(report.totalBytes)} B against your ${group(report.budgetBytes)} B ` +\n `budget — over by ${group(over)} B.`,\n );\n lines.push(` The budget is yours, from ${CONFIG_FILE_NAME}, and it caps the`);\n lines.push(' whole tree rather than one request — the stricter of the two, so');\n lines.push(' it cannot be met by moving bytes into another package. Exit 1 is');\n lines.push(' the same over-budget code every other smelt run uses.');\n }\n\n if (report.findings.length === 0) {\n lines.push('');\n lines.push(' no findings. Eight advisory rules ran and none matched — either the file is');\n lines.push(' in good shape, or a rule is asleep. `pnpm mutate` is how this repo tells the');\n lines.push(' difference about its own guards; a fixture per rule is how it tells it here.');\n return `${lines.join('\\n')}\\n`;\n }\n\n const rows = report.findings.map((finding) => ({\n place: `${finding.file}:${String(finding.line)}`,\n rule: finding.reason.rule,\n explanation: finding.reason.explanation,\n }));\n const ruleWidth = width(\n '',\n rows.map((row) => row.rule),\n );\n\n lines.push('');\n for (const row of rows) {\n lines.push(` ${row.rule.padEnd(ruleWidth)} ${row.place}`);\n // The explanation gets its own wrapped lines rather than a fourth column. It is\n // the *reason*, which is the part a reader actually has to read — clipped to a\n // terminal column it becomes an ellipsis, and Law 2 promises an explanation, not\n // the first 46 characters of one.\n for (const wrapped of wrap(row.explanation, EXPLANATION_WRAP)) lines.push(` ${wrapped}`);\n }\n\n lines.push('');\n lines.push(\n ` ${count(report.findings.length, 'finding')}. ` +\n (strict\n ? 'Exit 1: --strict was given.'\n : `Advisory — exit 0. Pass --strict to fail a CI run on any of them.`),\n );\n\n return `${lines.join('\\n')}\\n`;\n}\n\n/** The label the imperative count is reported under. Never \"instructions\": R6. */\nconst IMPERATIVES_LABEL = 'imperatives (heuristic)';\n\n/** The heaviest ancestor chain — the honest answer to \"what does a request cost\". */\nconst PER_REQUEST_LABEL = 'per request (worst case)';\n\n/** Every level summed. The repository's instruction surface, and not a request's cost. */\nconst WHOLE_TREE_LABEL = 'whole tree';\n\n/** Where a finding's explanation wraps, once its six-column indent is removed. */\nconst EXPLANATION_WRAP = 84;\n\n/** Greedy word wrap. No ICU, no dependency — the output is identical everywhere. */\nfunction wrap(text: string, columns: number): readonly string[] {\n const out: string[] = [];\n let line = '';\n for (const word of text.split(' ')) {\n const candidate = line === '' ? word : `${line} ${word}`;\n if (line !== '' && candidate.length > columns) {\n out.push(line);\n line = word;\n } else {\n line = candidate;\n }\n }\n if (line !== '') out.push(line);\n return out;\n}\n\n/** How a mirror stands beside its AGENTS.md, in one clause. */\nfunction mirrorNote(standing: AgentsMirrorReport['standing']): string {\n switch (standing) {\n case 'symlink':\n return 'symlink — cannot drift, and costs no extra bytes';\n case 'copy':\n return 'copy, byte-identical today — a symlink could not drift';\n case 'drift':\n return 'DIVERGED — see mirror-drift below';\n }\n}\n\n/** How many lines a byte range covers in the input. Derived, never tallied separately. */ function lineSpan(\n text: string,\n start: number,\n end: number,\n): number {\n const slice = Buffer.from(text, 'utf8').subarray(start, end).toString('utf8');\n return slice.split('\\n').length;\n}\n\nfunction width(header: string, values: readonly string[]): number {\n return values.reduce((widest, value) => Math.max(widest, value.length), header.length);\n}\n\n/** Thousands separators without pulling in ICU, so the output is identical everywhere. */\nfunction group(n: number): string {\n const digits = String(Math.abs(Math.trunc(n)));\n const grouped = digits.replace(/\\B(?=(\\d{3})+(?!\\d))/g, ',');\n return n < 0 ? `-${grouped}` : grouped;\n}\n\nfunction delta(inputBytes: number, outputBytes: number): string {\n if (inputBytes === 0) return 'empty input';\n const percent = ((outputBytes - inputBytes) / inputBytes) * 100;\n const sign = percent > 0 ? '+' : '';\n return `${sign}${percent.toFixed(1)}%`;\n}\n\nfunction count(n: number, noun: string, pluralSuffix = 's'): string {\n return `${group(n)} ${noun}${n === 1 ? '' : pluralSuffix}`;\n}\n\nfunction clip(text: string, max: number): string {\n return text.length <= max ? text : `${text.slice(0, max - 1)}…`;\n}\n"]}
|
package/dist/cli/run.d.ts
CHANGED
|
@@ -3,9 +3,9 @@ export { CLI_NAME, EXIT } from './shell.ts';
|
|
|
3
3
|
export type { AnswerStream, CliIo } from './shell.ts';
|
|
4
4
|
export { cliUsage } from './usage.ts';
|
|
5
5
|
export { parseSmeltArgs } from './args.ts';
|
|
6
|
-
export type { CliInvocation, HooksInvocation, InitInvocation, MapInvocation, RetrieveInvocation, SmeltInvocation, StatsInvocation, } from './args.ts';
|
|
7
|
-
export { formatMapReport, formatReport } from './report.ts';
|
|
8
|
-
export type { MapReportInput, ReportInput } from './report.ts';
|
|
6
|
+
export type { AgentsInvocation, CliInvocation, HooksInvocation, InitInvocation, MapInvocation, RetrieveInvocation, SmeltInvocation, StatsInvocation, } from './args.ts';
|
|
7
|
+
export { formatAgentsReport, formatMapReport, formatReport } from './report.ts';
|
|
8
|
+
export type { AgentsReportInput, MapReportInput, ReportInput } from './report.ts';
|
|
9
9
|
export { CLI_JSON_FORMAT, resolveRun } from './subcommands/smelt.ts';
|
|
10
10
|
export type { CliJsonEnvelope, ResolvedRun } from './subcommands/smelt.ts';
|
|
11
11
|
export { CLI_MAP_JSON_FORMAT, resolveMapRun } from './subcommands/map.ts';
|
|
@@ -14,6 +14,8 @@ export { resolveStoreRun } from './subcommands/retrieve.ts';
|
|
|
14
14
|
export type { ResolvedStoreRun } from './subcommands/retrieve.ts';
|
|
15
15
|
export { CLI_STATS_JSON_FORMAT } from './subcommands/stats.ts';
|
|
16
16
|
export type { CliStatsJsonEnvelope } from './subcommands/stats.ts';
|
|
17
|
+
export { CLI_AGENTS_JSON_FORMAT, resolveAgentsRun } from './subcommands/agents.ts';
|
|
18
|
+
export type { CliAgentsJsonEnvelope, ResolvedAgentsRun } from './subcommands/agents.ts';
|
|
17
19
|
/**
|
|
18
20
|
* The whole CLI, as a function that returns an exit code instead of calling `exit`.
|
|
19
21
|
*
|
package/dist/cli/run.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/cli/run.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAMxC,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAC5C,YAAY,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACtD,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC3C,YAAY,EACV,aAAa,EACb,eAAe,EACf,cAAc,EACd,aAAa,EACb,kBAAkB,EAClB,eAAe,EACf,eAAe,GAChB,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/cli/run.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAMxC,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAC5C,YAAY,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACtD,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC3C,YAAY,EACV,gBAAgB,EAChB,aAAa,EACb,eAAe,EACf,cAAc,EACd,aAAa,EACb,kBAAkB,EAClB,eAAe,EACf,eAAe,GAChB,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,kBAAkB,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChF,YAAY,EAAE,iBAAiB,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAClF,OAAO,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACrE,YAAY,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAC3E,OAAO,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAC1E,YAAY,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC/E,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAC5D,YAAY,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAClE,OAAO,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,YAAY,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AACnE,OAAO,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AACnF,YAAY,EAAE,qBAAqB,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAExF;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,MAAM,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,EAAE,EAAE,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,CA2BhF","sourcesContent":["import process from 'node:process';\n\nimport { CliUsageError, SmeltError } from '../errors.ts';\n\nimport { loadNearestConfig } from './config.ts';\nimport { EXIT } from './shell.ts';\nimport type { CliIo } from './shell.ts';\nimport { commandFor } from './subcommands/registry.ts';\nimport { cliUsage } from './usage.ts';\n\nimport { parseSmeltArgs } from './args.ts';\n\nexport { CLI_NAME, EXIT } from './shell.ts';\nexport type { AnswerStream, CliIo } from './shell.ts';\nexport { cliUsage } from './usage.ts';\nexport { parseSmeltArgs } from './args.ts';\nexport type {\n AgentsInvocation,\n CliInvocation,\n HooksInvocation,\n InitInvocation,\n MapInvocation,\n RetrieveInvocation,\n SmeltInvocation,\n StatsInvocation,\n} from './args.ts';\nexport { formatAgentsReport, formatMapReport, formatReport } from './report.ts';\nexport type { AgentsReportInput, MapReportInput, ReportInput } from './report.ts';\nexport { CLI_JSON_FORMAT, resolveRun } from './subcommands/smelt.ts';\nexport type { CliJsonEnvelope, ResolvedRun } from './subcommands/smelt.ts';\nexport { CLI_MAP_JSON_FORMAT, resolveMapRun } from './subcommands/map.ts';\nexport type { CliMapJsonEnvelope, ResolvedMapRun } from './subcommands/map.ts';\nexport { resolveStoreRun } from './subcommands/retrieve.ts';\nexport type { ResolvedStoreRun } from './subcommands/retrieve.ts';\nexport { CLI_STATS_JSON_FORMAT } from './subcommands/stats.ts';\nexport type { CliStatsJsonEnvelope } from './subcommands/stats.ts';\nexport { CLI_AGENTS_JSON_FORMAT, resolveAgentsRun } from './subcommands/agents.ts';\nexport type { CliAgentsJsonEnvelope, ResolvedAgentsRun } from './subcommands/agents.ts';\n\n/**\n * The whole CLI, as a function that returns an exit code instead of calling `exit`.\n *\n * Smelted text goes to stdout and the report goes to stderr, so `smelt big.log\n * --budget 4000 > small.log` leaves the human-readable part on the terminal and the\n * payload in the file.\n *\n * It is a lookup and a dispatch, and nothing else: this function used to hold a\n * `switch` over seven modes and seven `run*` functions beneath it, so adding a verb\n * meant editing it. Now the verb that parsed an invocation is the verb that resolves\n * and runs it — `SUBCOMMANDS` in `subcommands/registry.ts` — and the only decisions\n * left here are the two global flags and the mapping from a thrown error to an exit\n * code.\n *\n * The config is passed as a thunk, not a value: `init` and `hooks` read\n * `smelt.config.json` themselves with their own tolerance, and loading it eagerly here\n * would make a wizard you run to *fix* a malformed config refuse to start.\n */\nexport async function runCli(argv: readonly string[], io: CliIo): Promise<number> {\n try {\n const invocation = parseSmeltArgs(argv);\n\n if (invocation.mode === 'help') {\n io.stdout(cliUsage());\n return EXIT.ok;\n }\n if (invocation.mode === 'version') {\n io.stdout(`${io.version}\\n`);\n return EXIT.ok;\n }\n\n const command = commandFor(invocation.mode);\n const config = () => loadNearestConfig(io.cwd ?? process.cwd());\n return await command.run(command.resolve(invocation, config), io);\n } catch (error) {\n if (error instanceof CliUsageError) {\n io.stderr(`${error.message}\\n`);\n return EXIT.usage;\n }\n if (error instanceof SmeltError) {\n io.stderr(`${error.name}: ${error.message}\\n`);\n return EXIT.refused;\n }\n throw error;\n }\n}\n"]}
|
package/dist/cli/run.js
CHANGED
|
@@ -8,11 +8,12 @@ import { parseSmeltArgs } from './args.js';
|
|
|
8
8
|
export { CLI_NAME, EXIT } from './shell.js';
|
|
9
9
|
export { cliUsage } from './usage.js';
|
|
10
10
|
export { parseSmeltArgs } from './args.js';
|
|
11
|
-
export { formatMapReport, formatReport } from './report.js';
|
|
11
|
+
export { formatAgentsReport, formatMapReport, formatReport } from './report.js';
|
|
12
12
|
export { CLI_JSON_FORMAT, resolveRun } from './subcommands/smelt.js';
|
|
13
13
|
export { CLI_MAP_JSON_FORMAT, resolveMapRun } from './subcommands/map.js';
|
|
14
14
|
export { resolveStoreRun } from './subcommands/retrieve.js';
|
|
15
15
|
export { CLI_STATS_JSON_FORMAT } from './subcommands/stats.js';
|
|
16
|
+
export { CLI_AGENTS_JSON_FORMAT, resolveAgentsRun } from './subcommands/agents.js';
|
|
16
17
|
/**
|
|
17
18
|
* The whole CLI, as a function that returns an exit code instead of calling `exit`.
|
|
18
19
|
*
|
package/dist/cli/run.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run.js","sourceRoot":"","sources":["../../src/cli/run.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,cAAc,CAAC;AAEnC,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAEzD,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAElC,OAAO,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AACvD,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAEtC,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAE3C,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAE5C,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"run.js","sourceRoot":"","sources":["../../src/cli/run.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,cAAc,CAAC;AAEnC,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAEzD,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAElC,OAAO,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AACvD,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAEtC,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAE3C,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAE5C,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAW3C,OAAO,EAAE,kBAAkB,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhF,OAAO,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAErE,OAAO,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAE1E,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAE5D,OAAO,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAE/D,OAAO,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAGnF;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,CAAC,KAAK,UAAU,MAAM,CAAC,IAAuB,EAAE,EAAS;IAC7D,IAAI,CAAC;QACH,MAAM,UAAU,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;QAExC,IAAI,UAAU,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;YAC/B,EAAE,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC;YACtB,OAAO,IAAI,CAAC,EAAE,CAAC;QACjB,CAAC;QACD,IAAI,UAAU,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;YAClC,EAAE,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,OAAO,IAAI,CAAC,CAAC;YAC7B,OAAO,IAAI,CAAC,EAAE,CAAC;QACjB,CAAC;QAED,MAAM,OAAO,GAAG,UAAU,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;QAC5C,MAAM,MAAM,GAAG,GAAG,EAAE,CAAC,iBAAiB,CAAC,EAAE,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;QAChE,OAAO,MAAM,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,UAAU,EAAE,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC;IACpE,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,KAAK,YAAY,aAAa,EAAE,CAAC;YACnC,EAAE,CAAC,MAAM,CAAC,GAAG,KAAK,CAAC,OAAO,IAAI,CAAC,CAAC;YAChC,OAAO,IAAI,CAAC,KAAK,CAAC;QACpB,CAAC;QACD,IAAI,KAAK,YAAY,UAAU,EAAE,CAAC;YAChC,EAAE,CAAC,MAAM,CAAC,GAAG,KAAK,CAAC,IAAI,KAAK,KAAK,CAAC,OAAO,IAAI,CAAC,CAAC;YAC/C,OAAO,IAAI,CAAC,OAAO,CAAC;QACtB,CAAC;QACD,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC","sourcesContent":["import process from 'node:process';\n\nimport { CliUsageError, SmeltError } from '../errors.ts';\n\nimport { loadNearestConfig } from './config.ts';\nimport { EXIT } from './shell.ts';\nimport type { CliIo } from './shell.ts';\nimport { commandFor } from './subcommands/registry.ts';\nimport { cliUsage } from './usage.ts';\n\nimport { parseSmeltArgs } from './args.ts';\n\nexport { CLI_NAME, EXIT } from './shell.ts';\nexport type { AnswerStream, CliIo } from './shell.ts';\nexport { cliUsage } from './usage.ts';\nexport { parseSmeltArgs } from './args.ts';\nexport type {\n AgentsInvocation,\n CliInvocation,\n HooksInvocation,\n InitInvocation,\n MapInvocation,\n RetrieveInvocation,\n SmeltInvocation,\n StatsInvocation,\n} from './args.ts';\nexport { formatAgentsReport, formatMapReport, formatReport } from './report.ts';\nexport type { AgentsReportInput, MapReportInput, ReportInput } from './report.ts';\nexport { CLI_JSON_FORMAT, resolveRun } from './subcommands/smelt.ts';\nexport type { CliJsonEnvelope, ResolvedRun } from './subcommands/smelt.ts';\nexport { CLI_MAP_JSON_FORMAT, resolveMapRun } from './subcommands/map.ts';\nexport type { CliMapJsonEnvelope, ResolvedMapRun } from './subcommands/map.ts';\nexport { resolveStoreRun } from './subcommands/retrieve.ts';\nexport type { ResolvedStoreRun } from './subcommands/retrieve.ts';\nexport { CLI_STATS_JSON_FORMAT } from './subcommands/stats.ts';\nexport type { CliStatsJsonEnvelope } from './subcommands/stats.ts';\nexport { CLI_AGENTS_JSON_FORMAT, resolveAgentsRun } from './subcommands/agents.ts';\nexport type { CliAgentsJsonEnvelope, ResolvedAgentsRun } from './subcommands/agents.ts';\n\n/**\n * The whole CLI, as a function that returns an exit code instead of calling `exit`.\n *\n * Smelted text goes to stdout and the report goes to stderr, so `smelt big.log\n * --budget 4000 > small.log` leaves the human-readable part on the terminal and the\n * payload in the file.\n *\n * It is a lookup and a dispatch, and nothing else: this function used to hold a\n * `switch` over seven modes and seven `run*` functions beneath it, so adding a verb\n * meant editing it. Now the verb that parsed an invocation is the verb that resolves\n * and runs it — `SUBCOMMANDS` in `subcommands/registry.ts` — and the only decisions\n * left here are the two global flags and the mapping from a thrown error to an exit\n * code.\n *\n * The config is passed as a thunk, not a value: `init` and `hooks` read\n * `smelt.config.json` themselves with their own tolerance, and loading it eagerly here\n * would make a wizard you run to *fix* a malformed config refuse to start.\n */\nexport async function runCli(argv: readonly string[], io: CliIo): Promise<number> {\n try {\n const invocation = parseSmeltArgs(argv);\n\n if (invocation.mode === 'help') {\n io.stdout(cliUsage());\n return EXIT.ok;\n }\n if (invocation.mode === 'version') {\n io.stdout(`${io.version}\\n`);\n return EXIT.ok;\n }\n\n const command = commandFor(invocation.mode);\n const config = () => loadNearestConfig(io.cwd ?? process.cwd());\n return await command.run(command.resolve(invocation, config), io);\n } catch (error) {\n if (error instanceof CliUsageError) {\n io.stderr(`${error.message}\\n`);\n return EXIT.usage;\n }\n if (error instanceof SmeltError) {\n io.stderr(`${error.name}: ${error.message}\\n`);\n return EXIT.refused;\n }\n throw error;\n }\n}\n"]}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { AgentsLintReport } from '../../agents/lint.ts';
|
|
2
|
+
import type { LoadedConfig } from '../config.ts';
|
|
3
|
+
import type { Subcommand } from './subcommand.ts';
|
|
4
|
+
/**
|
|
5
|
+
* `smelt agents` — the instruction files an agent loads on **every** request.
|
|
6
|
+
*
|
|
7
|
+
* The verb exists because `AGENTS.md` is the one blob in a repository that is paid for
|
|
8
|
+
* on every single request, relevant or not, and nothing measures it. That is smelt's
|
|
9
|
+
* own subject, so the fit is exact — and the two actions divide along the line smelt
|
|
10
|
+
* divides everything else along:
|
|
11
|
+
*
|
|
12
|
+
* - **`lint`** measures and explains. It never edits.
|
|
13
|
+
* - **`split`** edits, mechanically, under `smelt init`'s consent discipline — and
|
|
14
|
+
* says plainly which half of the refactor it refused to do.
|
|
15
|
+
*
|
|
16
|
+
* There is no `smelt agents init`, and there will not be one: the guide this verb
|
|
17
|
+
* lints against says in as many words never to auto-generate an AGENTS.md, and a tool
|
|
18
|
+
* that built the thing its own source warns against would be worth less than no tool
|
|
19
|
+
* (ruling R1).
|
|
20
|
+
*/
|
|
21
|
+
/** `smelt agents <lint|split> [path]` — parsed. */
|
|
22
|
+
export interface AgentsInvocation {
|
|
23
|
+
readonly mode: 'agents';
|
|
24
|
+
readonly action: 'lint' | 'split';
|
|
25
|
+
/** The directory to read. Defaults to `.`; always a directory, never a file. */
|
|
26
|
+
readonly dir: string;
|
|
27
|
+
/** `lint` only: any finding exits 1. */
|
|
28
|
+
readonly strict: boolean;
|
|
29
|
+
/** `lint` only: the versioned envelope instead of the text report. */
|
|
30
|
+
readonly json: boolean;
|
|
31
|
+
}
|
|
32
|
+
/** Everything one `smelt agents lint` run needs, fully merged. */
|
|
33
|
+
export interface ResolvedAgentsRun {
|
|
34
|
+
readonly action: 'lint' | 'split';
|
|
35
|
+
readonly dir: string;
|
|
36
|
+
readonly strict: boolean;
|
|
37
|
+
readonly json: boolean;
|
|
38
|
+
/**
|
|
39
|
+
* The user's byte ceiling for the merged set, from `smelt.config.json`.
|
|
40
|
+
*
|
|
41
|
+
* **Absent means unbudgeted, and there is no built-in fallback** (ruling R2). This
|
|
42
|
+
* field carries no `budgetSource`, unlike {@link ResolvedRun}'s, precisely because
|
|
43
|
+
* there is only one source it can have come from: a budget here is always the
|
|
44
|
+
* user's, never a flag and never smelt's.
|
|
45
|
+
*/
|
|
46
|
+
readonly budgetBytes?: number;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* The `smelt agents lint --json` envelope format. Its own version line, like the map
|
|
50
|
+
* and stats envelopes: a lint report carries rule ids and byte counts per level, a
|
|
51
|
+
* structure that has to be able to move without dragging the other two with it.
|
|
52
|
+
*/
|
|
53
|
+
export declare const CLI_AGENTS_JSON_FORMAT = "smelt-agents-cli/v1";
|
|
54
|
+
/** What `smelt agents lint --json` prints. */
|
|
55
|
+
export interface CliAgentsJsonEnvelope {
|
|
56
|
+
readonly format: string;
|
|
57
|
+
/** The {@link AgentsLintReport} exactly as `lintAgents` returned it. */
|
|
58
|
+
readonly report: AgentsLintReport;
|
|
59
|
+
}
|
|
60
|
+
export declare const agentsCommand: Subcommand<AgentsInvocation, ResolvedAgentsRun>;
|
|
61
|
+
/**
|
|
62
|
+
* Merge one `'agents'` invocation with the loaded config.
|
|
63
|
+
*
|
|
64
|
+
* The whole merge is one key, and it has **no flag** on purpose. A `--budget` here
|
|
65
|
+
* would be a ceiling typed on the command line, which is a ceiling nobody in the
|
|
66
|
+
* repository has agreed to; the number that matters is the one committed in
|
|
67
|
+
* `smelt.config.json` where CI and every contributor read the same value. So unlike
|
|
68
|
+
* every other verb with a budget, this one cannot refuse for a missing budget: absent
|
|
69
|
+
* is a legitimate state, and it means "measure, do not fail".
|
|
70
|
+
*/
|
|
71
|
+
export declare function resolveAgentsRun(invocation: AgentsInvocation, config: LoadedConfig | undefined): ResolvedAgentsRun;
|
|
72
|
+
//# sourceMappingURL=agents.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agents.d.ts","sourceRoot":"","sources":["../../../src/cli/subcommands/agents.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAK7D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAMjD,OAAO,KAAK,EAAgB,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAEhE;;;;;;;;;;;;;;;;GAgBG;AAEH,mDAAmD;AACnD,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC;IAClC,gFAAgF;IAChF,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,wCAAwC;IACxC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,sEAAsE;IACtE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;CACxB;AAED,kEAAkE;AAClE,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC;IAClC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB;;;;;;;OAOG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,wBAAwB,CAAC;AAE5D,8CAA8C;AAC9C,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,wEAAwE;IACxE,QAAQ,CAAC,MAAM,EAAE,gBAAgB,CAAC;CACnC;AAED,eAAO,MAAM,aAAa,EAAE,UAAU,CAAC,gBAAgB,EAAE,iBAAiB,CAsFzE,CAAC;AAEF;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAC9B,UAAU,EAAE,gBAAgB,EAC5B,MAAM,EAAE,YAAY,GAAG,SAAS,GAC/B,iBAAiB,CASnB","sourcesContent":["import process from 'node:process';\n\nimport { lintAgents, overBudgetBytes } from '../../agents/lint.ts';\nimport type { AgentsLintReport } from '../../agents/lint.ts';\nimport { CliUsageError } from '../../errors.ts';\nimport { readTree } from '../../ops/inputs.ts';\nimport { runAgentsSplit } from '../agents.ts';\nimport { CONFIG_FILE_NAME } from '../config.ts';\nimport type { LoadedConfig } from '../config.ts';\nimport { formatAgentsReport } from '../report.ts';\nimport { CLI_NAME, EXIT } from '../shell.ts';\nimport type { CliIo } from '../shell.ts';\n\nimport type { FlagValues } from './flags.ts';\nimport type { ConfigSource, Subcommand } from './subcommand.ts';\n\n/**\n * `smelt agents` — the instruction files an agent loads on **every** request.\n *\n * The verb exists because `AGENTS.md` is the one blob in a repository that is paid for\n * on every single request, relevant or not, and nothing measures it. That is smelt's\n * own subject, so the fit is exact — and the two actions divide along the line smelt\n * divides everything else along:\n *\n * - **`lint`** measures and explains. It never edits.\n * - **`split`** edits, mechanically, under `smelt init`'s consent discipline — and\n * says plainly which half of the refactor it refused to do.\n *\n * There is no `smelt agents init`, and there will not be one: the guide this verb\n * lints against says in as many words never to auto-generate an AGENTS.md, and a tool\n * that built the thing its own source warns against would be worth less than no tool\n * (ruling R1).\n */\n\n/** `smelt agents <lint|split> [path]` — parsed. */\nexport interface AgentsInvocation {\n readonly mode: 'agents';\n readonly action: 'lint' | 'split';\n /** The directory to read. Defaults to `.`; always a directory, never a file. */\n readonly dir: string;\n /** `lint` only: any finding exits 1. */\n readonly strict: boolean;\n /** `lint` only: the versioned envelope instead of the text report. */\n readonly json: boolean;\n}\n\n/** Everything one `smelt agents lint` run needs, fully merged. */\nexport interface ResolvedAgentsRun {\n readonly action: 'lint' | 'split';\n readonly dir: string;\n readonly strict: boolean;\n readonly json: boolean;\n /**\n * The user's byte ceiling for the merged set, from `smelt.config.json`.\n *\n * **Absent means unbudgeted, and there is no built-in fallback** (ruling R2). This\n * field carries no `budgetSource`, unlike {@link ResolvedRun}'s, precisely because\n * there is only one source it can have come from: a budget here is always the\n * user's, never a flag and never smelt's.\n */\n readonly budgetBytes?: number;\n}\n\n/**\n * The `smelt agents lint --json` envelope format. Its own version line, like the map\n * and stats envelopes: a lint report carries rule ids and byte counts per level, a\n * structure that has to be able to move without dragging the other two with it.\n */\nexport const CLI_AGENTS_JSON_FORMAT = 'smelt-agents-cli/v1';\n\n/** What `smelt agents lint --json` prints. */\nexport interface CliAgentsJsonEnvelope {\n readonly format: string;\n /** The {@link AgentsLintReport} exactly as `lintAgents` returned it. */\n readonly report: AgentsLintReport;\n}\n\nexport const agentsCommand: Subcommand<AgentsInvocation, ResolvedAgentsRun> = {\n name: 'agents',\n flags: ['strict', 'json'],\n refusal:\n `agents reads the instruction files in a tree and reports on them — it plans no ` +\n `elisions, stores nothing, and has no budget of its own to take: the only budget ` +\n `it honours is agents.budgetBytes in ${CONFIG_FILE_NAME}, which is yours.`,\n usage: {\n synopsis: ['agents lint [dir] [--strict] [--json]'],\n occasional: ['agents split [dir]'],\n section: {\n heading: 'AGENTS',\n body:\n ` ${CLI_NAME} agents lint audits the instruction files an agent loads on every\\n` +\n ` request — every AGENTS.md, CLAUDE.md and GEMINI.md in the tree, because a\\n` +\n ` nested one merges with the root. A merge runs up the tree and never across\\n` +\n ` it, so it reports bytes per level, the per-request worst case (the heaviest\\n` +\n ` level plus its ancestors — what one agent actually loads) and the whole-tree\\n` +\n ` surface, plus an imperative count labelled a heuristic. Then eight advisory\\n` +\n ` rules: dead-path and dead-link (path-like tokens and links resolved against\\n` +\n ` the real tree — the check nobody else makes, and the reason to run this in\\n` +\n ` CI), forcing-language, structure-dump, generated-boilerplate, language-rule,\\n` +\n ` mirror-drift and restated-at-level. Every finding carries a stable rule id\\n` +\n ` and a sentence citing the guide it applies\\n` +\n ` (aihero.dev/a-complete-guide-to-agents-md). Findings exit 0; --strict makes\\n` +\n ` any finding exit 1. There is no built-in size limit: set agents.budgetBytes\\n` +\n ` in ${CONFIG_FILE_NAME} and exceeding it exits 1, as every other ${CLI_NAME}\\n` +\n ` budget does. The guide's own \"~150-200 instructions\" figure is printed as a\\n` +\n ` citation and compared to nothing.\\n` +\n `\\n` +\n ` ${CLI_NAME} agents split does the MECHANICAL half of the guide's refactor:\\n` +\n ` partition the root file by ## heading into one Markdown file per section\\n` +\n ` under docs/, rewrite the relative links that moved a directory deeper, and\\n` +\n ` leave a link list behind. Same discipline as init and hooks — every file\\n` +\n ` listed, one confirm, an existing file never overwritten without a per-file\\n` +\n ` yes. It does NOT decide which sections are essential: that is a reading of\\n` +\n ` your project, so it needs a model, and ${CLI_NAME} has none by law. Instead it\\n` +\n ` prints the guide's own refactor prompt with your real section headings filled\\n` +\n ` in, for you to hand to your own agent. That seam is the point, not an\\n` +\n ` omission. There is no \\`${CLI_NAME} agents init\\`: the guide says never to\\n` +\n ` auto-generate an AGENTS.md.`,\n },\n },\n\n /**\n * An action, and at most one directory. `lint` defaults to `.` because the everyday\n * invocation is \"lint here\" — unlike `map`, which is always aimed somewhere.\n */\n parse(values: FlagValues, positionals: readonly string[]): AgentsInvocation {\n const action = positionals[1];\n if (action !== 'lint' && action !== 'split') {\n throw new CliUsageError(\n `${CLI_NAME}: agents needs an action — lint or split.\\n` +\n ` ${CLI_NAME} agents lint [dir] [--strict] [--json]\\n` +\n ` ${CLI_NAME} agents split [dir]`,\n );\n }\n if (positionals.length > 3) {\n throw new CliUsageError(\n `${CLI_NAME}: agents ${action} takes at most one directory, got ` +\n `${String(positionals.length - 2)} (${positionals.slice(2).join(', ')}).`,\n );\n }\n if (action === 'split' && (values.strict === true || values.json === true)) {\n throw new CliUsageError(\n `${CLI_NAME}: --strict and --json belong to \\`${CLI_NAME} agents lint\\`. ` +\n `split is interactive and writes files; a report format and a CI exit code ` +\n `have nothing to act on there.`,\n );\n }\n return {\n mode: 'agents',\n action,\n dir: positionals[2] ?? '.',\n strict: values.strict === true,\n json: values.json === true,\n };\n },\n\n resolve(invocation: AgentsInvocation, config: ConfigSource): ResolvedAgentsRun {\n return resolveAgentsRun(invocation, config());\n },\n\n run(resolved: ResolvedAgentsRun, io: CliIo): number | Promise<number> {\n return resolved.action === 'lint' ? runLint(resolved, io) : runSplit(resolved, io);\n },\n};\n\n/**\n * Merge one `'agents'` invocation with the loaded config.\n *\n * The whole merge is one key, and it has **no flag** on purpose. A `--budget` here\n * would be a ceiling typed on the command line, which is a ceiling nobody in the\n * repository has agreed to; the number that matters is the one committed in\n * `smelt.config.json` where CI and every contributor read the same value. So unlike\n * every other verb with a budget, this one cannot refuse for a missing budget: absent\n * is a legitimate state, and it means \"measure, do not fail\".\n */\nexport function resolveAgentsRun(\n invocation: AgentsInvocation,\n config: LoadedConfig | undefined,\n): ResolvedAgentsRun {\n const budgetBytes = config?.config.agents?.budgetBytes;\n return {\n action: invocation.action,\n dir: invocation.dir,\n strict: invocation.strict,\n json: invocation.json,\n ...(budgetBytes === undefined ? {} : { budgetBytes }),\n };\n}\n\n/**\n * One `smelt agents lint` run: prove the target is a tree, lint it, render, exit.\n *\n * Three exit codes, and the reasoning for each:\n *\n * - **1 over the user's budget.** Identical to a `smelt` run that did not fit, and\n * identical for the same reason: not an error, not a success, and a script must be\n * able to tell without parsing prose. It applies with or without `--strict`,\n * because the number came from the repository rather than from smelt.\n * - **1 with `--strict` and any finding.** The CI switch. Off by default because the\n * rules are advisory heuristics.\n * - **0** otherwise, findings and all.\n */\nfunction runLint(run: ResolvedAgentsRun, io: CliIo): number {\n const tree = readTree(run.dir, run.dir, {\n tree: 'agents lint',\n file: `\\`${CLI_NAME} <file>\\``,\n });\n if (!tree.ok) throw new CliUsageError(`${CLI_NAME}: ${tree.refusal}`);\n\n const report = lintAgents({\n root: tree.value,\n ...(run.budgetBytes === undefined ? {} : { budgetBytes: run.budgetBytes }),\n });\n\n if (run.json) {\n const envelope: CliAgentsJsonEnvelope = { format: CLI_AGENTS_JSON_FORMAT, report };\n io.stdout(`${JSON.stringify(envelope, null, 2)}\\n`);\n } else {\n io.stdout(formatAgentsReport(report, { source: run.dir, strict: run.strict }));\n }\n\n if (overBudgetBytes(report) !== undefined) return EXIT.overBudget;\n if (run.strict && report.findings.length > 0) return EXIT.overBudget;\n return EXIT.ok;\n}\n\n/** `smelt agents split` is interactive like `init` and `hooks`, and refuses without a stream. */\nasync function runSplit(run: ResolvedAgentsRun, io: CliIo): Promise<number> {\n if (io.initInput === undefined) {\n throw new CliUsageError(\n `${CLI_NAME}: agents split is interactive — it lists every file it would write ` +\n `and asks before each one — and this invocation has no interactive input ` +\n `stream. Run \\`${CLI_NAME} agents split\\` from a terminal.`,\n );\n }\n return await runAgentsSplit({\n input: io.initInput,\n output: io.stdout,\n cwd: io.cwd ?? process.cwd(),\n dir: run.dir,\n });\n}\n"]}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import process from 'node:process';
|
|
2
|
+
import { lintAgents, overBudgetBytes } from '../../agents/lint.js';
|
|
3
|
+
import { CliUsageError } from '../../errors.js';
|
|
4
|
+
import { readTree } from '../../ops/inputs.js';
|
|
5
|
+
import { runAgentsSplit } from '../agents.js';
|
|
6
|
+
import { CONFIG_FILE_NAME } from '../config.js';
|
|
7
|
+
import { formatAgentsReport } from '../report.js';
|
|
8
|
+
import { CLI_NAME, EXIT } from '../shell.js';
|
|
9
|
+
/**
|
|
10
|
+
* The `smelt agents lint --json` envelope format. Its own version line, like the map
|
|
11
|
+
* and stats envelopes: a lint report carries rule ids and byte counts per level, a
|
|
12
|
+
* structure that has to be able to move without dragging the other two with it.
|
|
13
|
+
*/
|
|
14
|
+
export const CLI_AGENTS_JSON_FORMAT = 'smelt-agents-cli/v1';
|
|
15
|
+
export const agentsCommand = {
|
|
16
|
+
name: 'agents',
|
|
17
|
+
flags: ['strict', 'json'],
|
|
18
|
+
refusal: `agents reads the instruction files in a tree and reports on them — it plans no ` +
|
|
19
|
+
`elisions, stores nothing, and has no budget of its own to take: the only budget ` +
|
|
20
|
+
`it honours is agents.budgetBytes in ${CONFIG_FILE_NAME}, which is yours.`,
|
|
21
|
+
usage: {
|
|
22
|
+
synopsis: ['agents lint [dir] [--strict] [--json]'],
|
|
23
|
+
occasional: ['agents split [dir]'],
|
|
24
|
+
section: {
|
|
25
|
+
heading: 'AGENTS',
|
|
26
|
+
body: ` ${CLI_NAME} agents lint audits the instruction files an agent loads on every\n` +
|
|
27
|
+
` request — every AGENTS.md, CLAUDE.md and GEMINI.md in the tree, because a\n` +
|
|
28
|
+
` nested one merges with the root. A merge runs up the tree and never across\n` +
|
|
29
|
+
` it, so it reports bytes per level, the per-request worst case (the heaviest\n` +
|
|
30
|
+
` level plus its ancestors — what one agent actually loads) and the whole-tree\n` +
|
|
31
|
+
` surface, plus an imperative count labelled a heuristic. Then eight advisory\n` +
|
|
32
|
+
` rules: dead-path and dead-link (path-like tokens and links resolved against\n` +
|
|
33
|
+
` the real tree — the check nobody else makes, and the reason to run this in\n` +
|
|
34
|
+
` CI), forcing-language, structure-dump, generated-boilerplate, language-rule,\n` +
|
|
35
|
+
` mirror-drift and restated-at-level. Every finding carries a stable rule id\n` +
|
|
36
|
+
` and a sentence citing the guide it applies\n` +
|
|
37
|
+
` (aihero.dev/a-complete-guide-to-agents-md). Findings exit 0; --strict makes\n` +
|
|
38
|
+
` any finding exit 1. There is no built-in size limit: set agents.budgetBytes\n` +
|
|
39
|
+
` in ${CONFIG_FILE_NAME} and exceeding it exits 1, as every other ${CLI_NAME}\n` +
|
|
40
|
+
` budget does. The guide's own "~150-200 instructions" figure is printed as a\n` +
|
|
41
|
+
` citation and compared to nothing.\n` +
|
|
42
|
+
`\n` +
|
|
43
|
+
` ${CLI_NAME} agents split does the MECHANICAL half of the guide's refactor:\n` +
|
|
44
|
+
` partition the root file by ## heading into one Markdown file per section\n` +
|
|
45
|
+
` under docs/, rewrite the relative links that moved a directory deeper, and\n` +
|
|
46
|
+
` leave a link list behind. Same discipline as init and hooks — every file\n` +
|
|
47
|
+
` listed, one confirm, an existing file never overwritten without a per-file\n` +
|
|
48
|
+
` yes. It does NOT decide which sections are essential: that is a reading of\n` +
|
|
49
|
+
` your project, so it needs a model, and ${CLI_NAME} has none by law. Instead it\n` +
|
|
50
|
+
` prints the guide's own refactor prompt with your real section headings filled\n` +
|
|
51
|
+
` in, for you to hand to your own agent. That seam is the point, not an\n` +
|
|
52
|
+
` omission. There is no \`${CLI_NAME} agents init\`: the guide says never to\n` +
|
|
53
|
+
` auto-generate an AGENTS.md.`,
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
/**
|
|
57
|
+
* An action, and at most one directory. `lint` defaults to `.` because the everyday
|
|
58
|
+
* invocation is "lint here" — unlike `map`, which is always aimed somewhere.
|
|
59
|
+
*/
|
|
60
|
+
parse(values, positionals) {
|
|
61
|
+
const action = positionals[1];
|
|
62
|
+
if (action !== 'lint' && action !== 'split') {
|
|
63
|
+
throw new CliUsageError(`${CLI_NAME}: agents needs an action — lint or split.\n` +
|
|
64
|
+
` ${CLI_NAME} agents lint [dir] [--strict] [--json]\n` +
|
|
65
|
+
` ${CLI_NAME} agents split [dir]`);
|
|
66
|
+
}
|
|
67
|
+
if (positionals.length > 3) {
|
|
68
|
+
throw new CliUsageError(`${CLI_NAME}: agents ${action} takes at most one directory, got ` +
|
|
69
|
+
`${String(positionals.length - 2)} (${positionals.slice(2).join(', ')}).`);
|
|
70
|
+
}
|
|
71
|
+
if (action === 'split' && (values.strict === true || values.json === true)) {
|
|
72
|
+
throw new CliUsageError(`${CLI_NAME}: --strict and --json belong to \`${CLI_NAME} agents lint\`. ` +
|
|
73
|
+
`split is interactive and writes files; a report format and a CI exit code ` +
|
|
74
|
+
`have nothing to act on there.`);
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
mode: 'agents',
|
|
78
|
+
action,
|
|
79
|
+
dir: positionals[2] ?? '.',
|
|
80
|
+
strict: values.strict === true,
|
|
81
|
+
json: values.json === true,
|
|
82
|
+
};
|
|
83
|
+
},
|
|
84
|
+
resolve(invocation, config) {
|
|
85
|
+
return resolveAgentsRun(invocation, config());
|
|
86
|
+
},
|
|
87
|
+
run(resolved, io) {
|
|
88
|
+
return resolved.action === 'lint' ? runLint(resolved, io) : runSplit(resolved, io);
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
/**
|
|
92
|
+
* Merge one `'agents'` invocation with the loaded config.
|
|
93
|
+
*
|
|
94
|
+
* The whole merge is one key, and it has **no flag** on purpose. A `--budget` here
|
|
95
|
+
* would be a ceiling typed on the command line, which is a ceiling nobody in the
|
|
96
|
+
* repository has agreed to; the number that matters is the one committed in
|
|
97
|
+
* `smelt.config.json` where CI and every contributor read the same value. So unlike
|
|
98
|
+
* every other verb with a budget, this one cannot refuse for a missing budget: absent
|
|
99
|
+
* is a legitimate state, and it means "measure, do not fail".
|
|
100
|
+
*/
|
|
101
|
+
export function resolveAgentsRun(invocation, config) {
|
|
102
|
+
const budgetBytes = config?.config.agents?.budgetBytes;
|
|
103
|
+
return {
|
|
104
|
+
action: invocation.action,
|
|
105
|
+
dir: invocation.dir,
|
|
106
|
+
strict: invocation.strict,
|
|
107
|
+
json: invocation.json,
|
|
108
|
+
...(budgetBytes === undefined ? {} : { budgetBytes }),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* One `smelt agents lint` run: prove the target is a tree, lint it, render, exit.
|
|
113
|
+
*
|
|
114
|
+
* Three exit codes, and the reasoning for each:
|
|
115
|
+
*
|
|
116
|
+
* - **1 over the user's budget.** Identical to a `smelt` run that did not fit, and
|
|
117
|
+
* identical for the same reason: not an error, not a success, and a script must be
|
|
118
|
+
* able to tell without parsing prose. It applies with or without `--strict`,
|
|
119
|
+
* because the number came from the repository rather than from smelt.
|
|
120
|
+
* - **1 with `--strict` and any finding.** The CI switch. Off by default because the
|
|
121
|
+
* rules are advisory heuristics.
|
|
122
|
+
* - **0** otherwise, findings and all.
|
|
123
|
+
*/
|
|
124
|
+
function runLint(run, io) {
|
|
125
|
+
const tree = readTree(run.dir, run.dir, {
|
|
126
|
+
tree: 'agents lint',
|
|
127
|
+
file: `\`${CLI_NAME} <file>\``,
|
|
128
|
+
});
|
|
129
|
+
if (!tree.ok)
|
|
130
|
+
throw new CliUsageError(`${CLI_NAME}: ${tree.refusal}`);
|
|
131
|
+
const report = lintAgents({
|
|
132
|
+
root: tree.value,
|
|
133
|
+
...(run.budgetBytes === undefined ? {} : { budgetBytes: run.budgetBytes }),
|
|
134
|
+
});
|
|
135
|
+
if (run.json) {
|
|
136
|
+
const envelope = { format: CLI_AGENTS_JSON_FORMAT, report };
|
|
137
|
+
io.stdout(`${JSON.stringify(envelope, null, 2)}\n`);
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
io.stdout(formatAgentsReport(report, { source: run.dir, strict: run.strict }));
|
|
141
|
+
}
|
|
142
|
+
if (overBudgetBytes(report) !== undefined)
|
|
143
|
+
return EXIT.overBudget;
|
|
144
|
+
if (run.strict && report.findings.length > 0)
|
|
145
|
+
return EXIT.overBudget;
|
|
146
|
+
return EXIT.ok;
|
|
147
|
+
}
|
|
148
|
+
/** `smelt agents split` is interactive like `init` and `hooks`, and refuses without a stream. */
|
|
149
|
+
async function runSplit(run, io) {
|
|
150
|
+
if (io.initInput === undefined) {
|
|
151
|
+
throw new CliUsageError(`${CLI_NAME}: agents split is interactive — it lists every file it would write ` +
|
|
152
|
+
`and asks before each one — and this invocation has no interactive input ` +
|
|
153
|
+
`stream. Run \`${CLI_NAME} agents split\` from a terminal.`);
|
|
154
|
+
}
|
|
155
|
+
return await runAgentsSplit({
|
|
156
|
+
input: io.initInput,
|
|
157
|
+
output: io.stdout,
|
|
158
|
+
cwd: io.cwd ?? process.cwd(),
|
|
159
|
+
dir: run.dir,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
//# sourceMappingURL=agents.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agents.js","sourceRoot":"","sources":["../../../src/cli/subcommands/agents.ts"],"names":[],"mappings":"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","sourcesContent":["import process from 'node:process';\n\nimport { lintAgents, overBudgetBytes } from '../../agents/lint.ts';\nimport type { AgentsLintReport } from '../../agents/lint.ts';\nimport { CliUsageError } from '../../errors.ts';\nimport { readTree } from '../../ops/inputs.ts';\nimport { runAgentsSplit } from '../agents.ts';\nimport { CONFIG_FILE_NAME } from '../config.ts';\nimport type { LoadedConfig } from '../config.ts';\nimport { formatAgentsReport } from '../report.ts';\nimport { CLI_NAME, EXIT } from '../shell.ts';\nimport type { CliIo } from '../shell.ts';\n\nimport type { FlagValues } from './flags.ts';\nimport type { ConfigSource, Subcommand } from './subcommand.ts';\n\n/**\n * `smelt agents` — the instruction files an agent loads on **every** request.\n *\n * The verb exists because `AGENTS.md` is the one blob in a repository that is paid for\n * on every single request, relevant or not, and nothing measures it. That is smelt's\n * own subject, so the fit is exact — and the two actions divide along the line smelt\n * divides everything else along:\n *\n * - **`lint`** measures and explains. It never edits.\n * - **`split`** edits, mechanically, under `smelt init`'s consent discipline — and\n * says plainly which half of the refactor it refused to do.\n *\n * There is no `smelt agents init`, and there will not be one: the guide this verb\n * lints against says in as many words never to auto-generate an AGENTS.md, and a tool\n * that built the thing its own source warns against would be worth less than no tool\n * (ruling R1).\n */\n\n/** `smelt agents <lint|split> [path]` — parsed. */\nexport interface AgentsInvocation {\n readonly mode: 'agents';\n readonly action: 'lint' | 'split';\n /** The directory to read. Defaults to `.`; always a directory, never a file. */\n readonly dir: string;\n /** `lint` only: any finding exits 1. */\n readonly strict: boolean;\n /** `lint` only: the versioned envelope instead of the text report. */\n readonly json: boolean;\n}\n\n/** Everything one `smelt agents lint` run needs, fully merged. */\nexport interface ResolvedAgentsRun {\n readonly action: 'lint' | 'split';\n readonly dir: string;\n readonly strict: boolean;\n readonly json: boolean;\n /**\n * The user's byte ceiling for the merged set, from `smelt.config.json`.\n *\n * **Absent means unbudgeted, and there is no built-in fallback** (ruling R2). This\n * field carries no `budgetSource`, unlike {@link ResolvedRun}'s, precisely because\n * there is only one source it can have come from: a budget here is always the\n * user's, never a flag and never smelt's.\n */\n readonly budgetBytes?: number;\n}\n\n/**\n * The `smelt agents lint --json` envelope format. Its own version line, like the map\n * and stats envelopes: a lint report carries rule ids and byte counts per level, a\n * structure that has to be able to move without dragging the other two with it.\n */\nexport const CLI_AGENTS_JSON_FORMAT = 'smelt-agents-cli/v1';\n\n/** What `smelt agents lint --json` prints. */\nexport interface CliAgentsJsonEnvelope {\n readonly format: string;\n /** The {@link AgentsLintReport} exactly as `lintAgents` returned it. */\n readonly report: AgentsLintReport;\n}\n\nexport const agentsCommand: Subcommand<AgentsInvocation, ResolvedAgentsRun> = {\n name: 'agents',\n flags: ['strict', 'json'],\n refusal:\n `agents reads the instruction files in a tree and reports on them — it plans no ` +\n `elisions, stores nothing, and has no budget of its own to take: the only budget ` +\n `it honours is agents.budgetBytes in ${CONFIG_FILE_NAME}, which is yours.`,\n usage: {\n synopsis: ['agents lint [dir] [--strict] [--json]'],\n occasional: ['agents split [dir]'],\n section: {\n heading: 'AGENTS',\n body:\n ` ${CLI_NAME} agents lint audits the instruction files an agent loads on every\\n` +\n ` request — every AGENTS.md, CLAUDE.md and GEMINI.md in the tree, because a\\n` +\n ` nested one merges with the root. A merge runs up the tree and never across\\n` +\n ` it, so it reports bytes per level, the per-request worst case (the heaviest\\n` +\n ` level plus its ancestors — what one agent actually loads) and the whole-tree\\n` +\n ` surface, plus an imperative count labelled a heuristic. Then eight advisory\\n` +\n ` rules: dead-path and dead-link (path-like tokens and links resolved against\\n` +\n ` the real tree — the check nobody else makes, and the reason to run this in\\n` +\n ` CI), forcing-language, structure-dump, generated-boilerplate, language-rule,\\n` +\n ` mirror-drift and restated-at-level. Every finding carries a stable rule id\\n` +\n ` and a sentence citing the guide it applies\\n` +\n ` (aihero.dev/a-complete-guide-to-agents-md). Findings exit 0; --strict makes\\n` +\n ` any finding exit 1. There is no built-in size limit: set agents.budgetBytes\\n` +\n ` in ${CONFIG_FILE_NAME} and exceeding it exits 1, as every other ${CLI_NAME}\\n` +\n ` budget does. The guide's own \"~150-200 instructions\" figure is printed as a\\n` +\n ` citation and compared to nothing.\\n` +\n `\\n` +\n ` ${CLI_NAME} agents split does the MECHANICAL half of the guide's refactor:\\n` +\n ` partition the root file by ## heading into one Markdown file per section\\n` +\n ` under docs/, rewrite the relative links that moved a directory deeper, and\\n` +\n ` leave a link list behind. Same discipline as init and hooks — every file\\n` +\n ` listed, one confirm, an existing file never overwritten without a per-file\\n` +\n ` yes. It does NOT decide which sections are essential: that is a reading of\\n` +\n ` your project, so it needs a model, and ${CLI_NAME} has none by law. Instead it\\n` +\n ` prints the guide's own refactor prompt with your real section headings filled\\n` +\n ` in, for you to hand to your own agent. That seam is the point, not an\\n` +\n ` omission. There is no \\`${CLI_NAME} agents init\\`: the guide says never to\\n` +\n ` auto-generate an AGENTS.md.`,\n },\n },\n\n /**\n * An action, and at most one directory. `lint` defaults to `.` because the everyday\n * invocation is \"lint here\" — unlike `map`, which is always aimed somewhere.\n */\n parse(values: FlagValues, positionals: readonly string[]): AgentsInvocation {\n const action = positionals[1];\n if (action !== 'lint' && action !== 'split') {\n throw new CliUsageError(\n `${CLI_NAME}: agents needs an action — lint or split.\\n` +\n ` ${CLI_NAME} agents lint [dir] [--strict] [--json]\\n` +\n ` ${CLI_NAME} agents split [dir]`,\n );\n }\n if (positionals.length > 3) {\n throw new CliUsageError(\n `${CLI_NAME}: agents ${action} takes at most one directory, got ` +\n `${String(positionals.length - 2)} (${positionals.slice(2).join(', ')}).`,\n );\n }\n if (action === 'split' && (values.strict === true || values.json === true)) {\n throw new CliUsageError(\n `${CLI_NAME}: --strict and --json belong to \\`${CLI_NAME} agents lint\\`. ` +\n `split is interactive and writes files; a report format and a CI exit code ` +\n `have nothing to act on there.`,\n );\n }\n return {\n mode: 'agents',\n action,\n dir: positionals[2] ?? '.',\n strict: values.strict === true,\n json: values.json === true,\n };\n },\n\n resolve(invocation: AgentsInvocation, config: ConfigSource): ResolvedAgentsRun {\n return resolveAgentsRun(invocation, config());\n },\n\n run(resolved: ResolvedAgentsRun, io: CliIo): number | Promise<number> {\n return resolved.action === 'lint' ? runLint(resolved, io) : runSplit(resolved, io);\n },\n};\n\n/**\n * Merge one `'agents'` invocation with the loaded config.\n *\n * The whole merge is one key, and it has **no flag** on purpose. A `--budget` here\n * would be a ceiling typed on the command line, which is a ceiling nobody in the\n * repository has agreed to; the number that matters is the one committed in\n * `smelt.config.json` where CI and every contributor read the same value. So unlike\n * every other verb with a budget, this one cannot refuse for a missing budget: absent\n * is a legitimate state, and it means \"measure, do not fail\".\n */\nexport function resolveAgentsRun(\n invocation: AgentsInvocation,\n config: LoadedConfig | undefined,\n): ResolvedAgentsRun {\n const budgetBytes = config?.config.agents?.budgetBytes;\n return {\n action: invocation.action,\n dir: invocation.dir,\n strict: invocation.strict,\n json: invocation.json,\n ...(budgetBytes === undefined ? {} : { budgetBytes }),\n };\n}\n\n/**\n * One `smelt agents lint` run: prove the target is a tree, lint it, render, exit.\n *\n * Three exit codes, and the reasoning for each:\n *\n * - **1 over the user's budget.** Identical to a `smelt` run that did not fit, and\n * identical for the same reason: not an error, not a success, and a script must be\n * able to tell without parsing prose. It applies with or without `--strict`,\n * because the number came from the repository rather than from smelt.\n * - **1 with `--strict` and any finding.** The CI switch. Off by default because the\n * rules are advisory heuristics.\n * - **0** otherwise, findings and all.\n */\nfunction runLint(run: ResolvedAgentsRun, io: CliIo): number {\n const tree = readTree(run.dir, run.dir, {\n tree: 'agents lint',\n file: `\\`${CLI_NAME} <file>\\``,\n });\n if (!tree.ok) throw new CliUsageError(`${CLI_NAME}: ${tree.refusal}`);\n\n const report = lintAgents({\n root: tree.value,\n ...(run.budgetBytes === undefined ? {} : { budgetBytes: run.budgetBytes }),\n });\n\n if (run.json) {\n const envelope: CliAgentsJsonEnvelope = { format: CLI_AGENTS_JSON_FORMAT, report };\n io.stdout(`${JSON.stringify(envelope, null, 2)}\\n`);\n } else {\n io.stdout(formatAgentsReport(report, { source: run.dir, strict: run.strict }));\n }\n\n if (overBudgetBytes(report) !== undefined) return EXIT.overBudget;\n if (run.strict && report.findings.length > 0) return EXIT.overBudget;\n return EXIT.ok;\n}\n\n/** `smelt agents split` is interactive like `init` and `hooks`, and refuses without a stream. */\nasync function runSplit(run: ResolvedAgentsRun, io: CliIo): Promise<number> {\n if (io.initInput === undefined) {\n throw new CliUsageError(\n `${CLI_NAME}: agents split is interactive — it lists every file it would write ` +\n `and asks before each one — and this invocation has no interactive input ` +\n `stream. Run \\`${CLI_NAME} agents split\\` from a terminal.`,\n );\n }\n return await runAgentsSplit({\n input: io.initInput,\n output: io.stdout,\n cwd: io.cwd ?? process.cwd(),\n dir: run.dir,\n });\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"flags.d.ts","sourceRoot":"","sources":["../../../src/cli/subcommands/flags.ts"],"names":[],"mappings":"AAUA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,SAAS;aACpB,MAAM;iBAAI,IAAI,EAAE,QAAQ;;aACxB,KAAK;iBAAI,IAAI,EAAE,QAAQ;iBAAE,QAAQ;;aACjC,QAAQ;iBAAI,IAAI,EAAE,QAAQ;;aAC1B,QAAQ;iBAAI,IAAI,EAAE,QAAQ;;aAC1B,MAAM;iBAAI,IAAI,EAAE,QAAQ;iBAAE,QAAQ;;aAClC,KAAK;iBAAI,IAAI,EAAE,QAAQ;;aACvB,OAAO;iBAAI,IAAI,EAAE,QAAQ;;aACzB,IAAI;iBAAI,IAAI,EAAE,SAAS;;aACvB,WAAW;iBAAI,IAAI,EAAE,SAAS;;aAC9B,IAAI;iBAAI,IAAI,EAAE,SAAS;iBAAE,KAAK,EAAE,GAAG;;aACnC,OAAO;iBAAI,IAAI,EAAE,SAAS;;CAClB,CAAC;AAEX,uFAAuF;AACvF,MAAM,MAAM,QAAQ,GAAG,MAAM,OAAO,SAAS,CAAC;AAE9C;;;;GAIG;AACH,eAAO,MAAM,YAAY,8BAA6D,CAAC;AAEvF,kEAAkE;AAClE,MAAM,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC,OAAO,YAAY,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;AAExE,oFAAoF;AACpF,eAAO,MAAM,UAAU,EAAE,SAAS,QAAQ,EAEzC,CAAC;AAEF;;;;;;;;;GASG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,SAAS,QAAQ,EAAE,GAAG,MAAM,CAI3D;AAED,iGAAiG;AACjG,KAAK,SAAS,CAAC,CAAC,IAAI,CAAC,SAAS;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAA;CAAE,GACtD,OAAO,GACP,CAAC,SAAS;IAAE,QAAQ,CAAC,QAAQ,EAAE,IAAI,CAAA;CAAE,GACnC,SAAS,MAAM,EAAE,GACjB,MAAM,CAAC;AAEb;;;;GAIG;AACH,MAAM,MAAM,UAAU,GAAG;IAAE,QAAQ,EAAE,CAAC,IAAI,QAAQ,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC,OAAO,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;CAAE,CAAC;AAEzF,6DAA6D;AAC7D,MAAM,WAAW,QAAQ;IACvB,gEAAgE;IAChE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB;;;;;OAKG;IACH,IAAI,IAAI,SAAS,MAAM,EAAE,CAAC;CAC3B;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAerF;AAKD;;;;GAIG;AACH,eAAO,MAAM,SAAS,EAAE,QAAQ,CAAC,MAAM,CAAC,QAAQ,EAAE,QAAQ,CAAC,CA8E1D,CAAC;AAEF;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAOvE","sourcesContent":["import { SUPPORTED_LANGUAGES } from '../../detect.ts';\nimport { CliUsageError } from '../../errors.ts';\nimport { HARNESS_IDS } from '../../harness/registry.ts';\nimport { budgetFault, budgetMalformed } from '../../ops/inputs.ts';\nimport type { BudgetFault } from '../../ops/inputs.ts';\nimport { STRATEGIES, DEFAULT_STRATEGY } from '../../plan/planners.ts';\nimport { STRUCTURAL_LANGUAGES } from '../../plan/structural.ts';\nimport { DEFAULT_REPO_IGNORE } from '../../repomap/map.ts';\nimport { CLI_NAME } from '../shell.ts';\n\n/**\n * Every flag the CLI accepts, once.\n *\n * The table is the companion the {@link Subcommand} registry needs: a verb declares\n * the flags it owns as `FlagName[]`, so the type of that list — and therefore whether\n * a verb can claim a flag that does not exist — comes from here. It carries three\n * things that used to be written in three places:\n *\n * 1. **How `node:util.parseArgs` reads the flag.** The option table used to be a\n * literal inside `parseSmeltArgs`.\n * 2. **The name.** `FlagName` is `keyof typeof CLI_FLAGS`, so an eleventh flag is\n * spelled once and every verb's ownership list typechecks against it.\n * 3. **Its OPTIONS entry in `--help`.** Including the `map only.` / `hooks only.`\n * prefix, which is *not* stored here: it is generated from the registry's\n * ownership (see `renderOptions` in `cli/usage.ts`), because \"which verb owns\n * this flag\" is a fact the registry already holds and prose that restates it is\n * prose that can go stale.\n *\n * Key order is meaningful: it is the order the OPTIONS block renders and the order a\n * refusal lists flags in, so keep it stable and append new flags where they read best.\n */\nexport const CLI_FLAGS = {\n budget: { type: 'string' },\n focus: { type: 'string', multiple: true },\n language: { type: 'string' },\n strategy: { type: 'string' },\n ignore: { type: 'string', multiple: true },\n cache: { type: 'string' },\n harness: { type: 'string' },\n json: { type: 'boolean' },\n reconstruct: { type: 'boolean' },\n help: { type: 'boolean', short: 'h' },\n version: { type: 'boolean' },\n} as const;\n\n/** Every flag name, as a type. A verb cannot claim a flag that is not in the table. */\nexport type FlagName = keyof typeof CLI_FLAGS;\n\n/**\n * The flags answered *before* any verb, so no verb owns them and no verb may refuse\n * them: `smelt map --help` prints the help, exactly as it always has. Every other flag\n * belongs to at least one verb — `test/guards/subcommand-registry.test.ts` pins that.\n */\nexport const GLOBAL_FLAGS = ['help', 'version'] as const satisfies readonly FlagName[];\n\n/** A flag a verb can own — everything but the two global ones. */\nexport type VerbFlag = Exclude<FlagName, (typeof GLOBAL_FLAGS)[number]>;\n\n/** Every ownable flag, in table order: the list a refusal checks a verb against. */\nexport const VERB_FLAGS: readonly VerbFlag[] = Object.keys(CLI_FLAGS).filter(\n (name): name is VerbFlag => !(GLOBAL_FLAGS as readonly string[]).includes(name),\n);\n\n/**\n * `--a`, `--a and --b`, `--a, --b and --c` — in flag-table order, always.\n *\n * Every refusal that names more than one flag spells the list this way: the\n * ownership refusal in `./registry.ts`, and the default verb's `--reconstruct`\n * refusal in `./smelt.ts`. It lives with the table because the *order* is the\n * table's — a refusal that listed flags in the order the user happened to type them\n * would read differently every time, and two refusals disagreeing about how to spell\n * the same pair is the kind of drift this file exists to remove.\n */\nexport function flagList(flags: readonly VerbFlag[]): string {\n const spelled = VERB_FLAGS.filter((flag) => flags.includes(flag)).map((flag) => `--${flag}`);\n if (spelled.length <= 1) return spelled.join('');\n return `${spelled.slice(0, -1).join(', ')} and ${spelled.at(-1) ?? ''}`;\n}\n\n/** What one flag's parsed value looks like, derived from how `parseArgs` was told to read it. */\ntype FlagValue<F> = F extends { readonly type: 'boolean' }\n ? boolean\n : F extends { readonly multiple: true }\n ? readonly string[]\n : string;\n\n/**\n * The parsed flags, as every `Subcommand.parse` sees them: one optional field per\n * flag, typed by the table above. Absent means the user did not type it — which is\n * the only thing a refusal needs to know, and the only thing a verb may act on.\n */\nexport type FlagValues = { readonly [K in FlagName]?: FlagValue<(typeof CLI_FLAGS)[K]> };\n\n/** How one flag appears in the OPTIONS block of `--help`. */\nexport interface FlagHelp {\n /** The left column, e.g. `--budget <bytes>` or `-h, --help`. */\n readonly label: string;\n /**\n * The description, already wrapped to the OPTIONS column — a function because three\n * entries render a registry (`SUPPORTED_LANGUAGES`, `STRATEGIES` +\n * `STRUCTURAL_LANGUAGES`, `HARNESS_IDS`) rather than a hand-typed list. The\n * ownership prefix is not here; it is generated.\n */\n body(): readonly string[];\n}\n\n/**\n * A comma-separated list under an OPTIONS entry's hanging indent, wrapped where the\n * hand-typed version wrapped. `--strategy` and `--language` render their registries on\n * one line because they fit; the harness ids do not, and a list long enough to wrap is\n * exactly the list nobody keeps in sync by hand.\n */\nexport function optionList(items: readonly string[], width: number): readonly string[] {\n const lines: string[] = [];\n let line = '';\n items.forEach((item, index) => {\n const word = index === items.length - 1 ? `${item}.` : `${item},`;\n const candidate = line === '' ? word : `${line} ${word}`;\n if (line !== '' && candidate.length > width) {\n lines.push(line);\n line = word;\n } else {\n line = candidate;\n }\n });\n lines.push(line);\n return lines;\n}\n\n/** The width an OPTIONS description wraps within, once its 23-column indent is removed. */\nconst OPTION_BODY_WIDTH = 65;\n\n/**\n * The OPTIONS entry for every flag. `Record<FlagName, FlagHelp>` on purpose: a flag\n * added to {@link CLI_FLAGS} without a help entry is a compile error, so the help text\n * cannot fall behind what the parser accepts.\n */\nexport const FLAG_HELP: Readonly<Record<FlagName, FlagHelp>> = {\n budget: {\n label: '--budget <bytes>',\n body: () => [\n 'Required, unless smelt.config.json sets defaultBudgetBytes.',\n 'Soft ceiling for the output, in UTF-8 bytes (for map: a hard',\n 'ceiling, met by construction). No built-in default: a budget',\n `${CLI_NAME} invented would decide for you.`,\n ],\n },\n focus: {\n label: '--focus <term>',\n body: () => [\n 'What you were looking for. Repeatable. Matching regions and',\n 'their context survive; the runs between them collapse. For',\n 'map: symbols matching a term (by name or path) are promoted',\n 'to the front of the fill order, ranks unchanged.',\n ],\n },\n language: {\n label: '--language <id>',\n body: () => [`Override detection. One of: ${[...SUPPORTED_LANGUAGES, 'unknown'].join(', ')}.`],\n },\n strategy: {\n label: '--strategy <id>',\n body: () => [\n `${STRATEGIES.join(', ')}. Defaults to ${DEFAULT_STRATEGY}, unless`,\n 'smelt.config.json says otherwise. structural parses',\n `${STRUCTURAL_LANGUAGES.join(', ')};`,\n 'any other language is refused, never approximated. auto picks',\n 'structural for those languages and lexical for the rest, and',\n 'the report names whichever one actually ran.',\n ],\n },\n ignore: {\n label: '--ignore <entry>',\n body: () => [\n 'Repeatable. Replaces the default ignore list',\n // Read off DEFAULT_REPO_IGNORE, never re-typed: help text that lists a default\n // by hand is help text that will one day describe a different default.\n `(${DEFAULT_REPO_IGNORE.join(', ')}): a bare name matches any path segment,`,\n 'an entry containing / is a root-relative prefix.',\n ],\n },\n cache: {\n label: '--cache <dir>',\n body: () => [\n 'Directory for the tags cache, keyed by content',\n 'hash. Only when given does the map write to disk at all.',\n ],\n },\n harness: {\n label: '--harness <id>',\n body: () => [\n 'Skip harness detection and target one id:',\n ...optionList(HARNESS_IDS, OPTION_BODY_WIDTH),\n ],\n },\n json: {\n label: '--json',\n body: () => [\n 'Print a JSON envelope on stdout instead of the text:',\n '{ format, result, elided } for a smelt run — `result` is',\n 'the SmeltResult verbatim, `elided` carries the bytes, so',\n 'the envelope can be reconstructed; feed it back with',\n '--reconstruct. For map: { format, map }, the RepoMap',\n 'structure verbatim.',\n ],\n },\n reconstruct: {\n label: '--reconstruct',\n body: () => [\n 'Read a --json envelope and print the original text, byte for',\n 'byte. This is Law 3 you can run from a shell.',\n ],\n },\n help: { label: '-h, --help', body: () => ['This text.'] },\n version: { label: '--version', body: () => ['The package version.'] },\n};\n\n/**\n * `--budget` has no built-in default, for the same reason `smelt()` has none: a budget\n * smelt invented would be smelt deciding how much of the caller's context to throw\n * away, silently, at a number nobody chose. A *missing* flag is not an error here,\n * though — `smelt.config.json` may carry a `defaultBudgetBytes` the user chose\n * explicitly, and the verb's `resolve` errors only when neither exists. A malformed\n * value is always an error.\n *\n * It lives with the flag rather than with a verb because two verbs own `--budget`, and\n * the two of them agreeing on what \"4kb\" means is not something to leave to chance.\n *\n * The *lexing* is the CLI's own and stays here — argv carries strings, so `4kb` and a\n * leading `-` are answered by a digits-only test before anything numeric happens. The\n * *rule* and *the sentence that refuses it* come from `ops/inputs.ts`, which is also\n * where the `smelt_file` tool gets them: two surfaces, one law, spelled `--budget`\n * here and `\"budgetBytes\"` there.\n */\nexport function parseBudget(raw: string | undefined): number | undefined {\n if (raw === undefined) return undefined;\n if (!/^\\d+$/.test(raw)) throw refuseBudget('not-an-integer', raw);\n const value = Number(raw);\n const fault = budgetFault(value);\n if (fault !== undefined) throw refuseBudget(fault, raw);\n return value;\n}\n\n/** The malformed-budget refusal, in the CLI's currency: prefixed, and exit 2. */\nfunction refuseBudget(fault: BudgetFault, raw: string): CliUsageError {\n return new CliUsageError(`${CLI_NAME}: ${budgetMalformed(fault, '--budget', raw)}`);\n}\n"]}
|
|
1
|
+
{"version":3,"file":"flags.d.ts","sourceRoot":"","sources":["../../../src/cli/subcommands/flags.ts"],"names":[],"mappings":"AAUA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,SAAS;aACpB,MAAM;iBAAI,IAAI,EAAE,QAAQ;;aACxB,KAAK;iBAAI,IAAI,EAAE,QAAQ;iBAAE,QAAQ;;aACjC,QAAQ;iBAAI,IAAI,EAAE,QAAQ;;aAC1B,QAAQ;iBAAI,IAAI,EAAE,QAAQ;;aAC1B,MAAM;iBAAI,IAAI,EAAE,QAAQ;iBAAE,QAAQ;;aAClC,KAAK;iBAAI,IAAI,EAAE,QAAQ;;aACvB,OAAO;iBAAI,IAAI,EAAE,QAAQ;;aACzB,MAAM;iBAAI,IAAI,EAAE,SAAS;;aACzB,IAAI;iBAAI,IAAI,EAAE,SAAS;;aACvB,WAAW;iBAAI,IAAI,EAAE,SAAS;;aAC9B,IAAI;iBAAI,IAAI,EAAE,SAAS;iBAAE,KAAK,EAAE,GAAG;;aACnC,OAAO;iBAAI,IAAI,EAAE,SAAS;;CAClB,CAAC;AAEX,uFAAuF;AACvF,MAAM,MAAM,QAAQ,GAAG,MAAM,OAAO,SAAS,CAAC;AAE9C;;;;GAIG;AACH,eAAO,MAAM,YAAY,8BAA6D,CAAC;AAEvF,kEAAkE;AAClE,MAAM,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC,OAAO,YAAY,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;AAExE,oFAAoF;AACpF,eAAO,MAAM,UAAU,EAAE,SAAS,QAAQ,EAEzC,CAAC;AAEF;;;;;;;;;GASG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,SAAS,QAAQ,EAAE,GAAG,MAAM,CAI3D;AAED,iGAAiG;AACjG,KAAK,SAAS,CAAC,CAAC,IAAI,CAAC,SAAS;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAA;CAAE,GACtD,OAAO,GACP,CAAC,SAAS;IAAE,QAAQ,CAAC,QAAQ,EAAE,IAAI,CAAA;CAAE,GACnC,SAAS,MAAM,EAAE,GACjB,MAAM,CAAC;AAEb;;;;GAIG;AACH,MAAM,MAAM,UAAU,GAAG;IAAE,QAAQ,EAAE,CAAC,IAAI,QAAQ,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC,OAAO,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;CAAE,CAAC;AAEzF,6DAA6D;AAC7D,MAAM,WAAW,QAAQ;IACvB,gEAAgE;IAChE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB;;;;;OAKG;IACH,IAAI,IAAI,SAAS,MAAM,EAAE,CAAC;CAC3B;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAerF;AAKD;;;;GAIG;AACH,eAAO,MAAM,SAAS,EAAE,QAAQ,CAAC,MAAM,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAyF1D,CAAC;AAEF;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAOvE","sourcesContent":["import { SUPPORTED_LANGUAGES } from '../../detect.ts';\nimport { CliUsageError } from '../../errors.ts';\nimport { HARNESS_IDS } from '../../harness/registry.ts';\nimport { budgetFault, budgetMalformed } from '../../ops/inputs.ts';\nimport type { BudgetFault } from '../../ops/inputs.ts';\nimport { STRATEGIES, DEFAULT_STRATEGY } from '../../plan/planners.ts';\nimport { STRUCTURAL_LANGUAGES } from '../../plan/structural.ts';\nimport { DEFAULT_REPO_IGNORE } from '../../repomap/map.ts';\nimport { CLI_NAME } from '../shell.ts';\n\n/**\n * Every flag the CLI accepts, once.\n *\n * The table is the companion the {@link Subcommand} registry needs: a verb declares\n * the flags it owns as `FlagName[]`, so the type of that list — and therefore whether\n * a verb can claim a flag that does not exist — comes from here. It carries three\n * things that used to be written in three places:\n *\n * 1. **How `node:util.parseArgs` reads the flag.** The option table used to be a\n * literal inside `parseSmeltArgs`.\n * 2. **The name.** `FlagName` is `keyof typeof CLI_FLAGS`, so an eleventh flag is\n * spelled once and every verb's ownership list typechecks against it.\n * 3. **Its OPTIONS entry in `--help`.** Including the `map only.` / `hooks only.`\n * prefix, which is *not* stored here: it is generated from the registry's\n * ownership (see `renderOptions` in `cli/usage.ts`), because \"which verb owns\n * this flag\" is a fact the registry already holds and prose that restates it is\n * prose that can go stale.\n *\n * Key order is meaningful: it is the order the OPTIONS block renders and the order a\n * refusal lists flags in, so keep it stable and append new flags where they read best.\n */\nexport const CLI_FLAGS = {\n budget: { type: 'string' },\n focus: { type: 'string', multiple: true },\n language: { type: 'string' },\n strategy: { type: 'string' },\n ignore: { type: 'string', multiple: true },\n cache: { type: 'string' },\n harness: { type: 'string' },\n strict: { type: 'boolean' },\n json: { type: 'boolean' },\n reconstruct: { type: 'boolean' },\n help: { type: 'boolean', short: 'h' },\n version: { type: 'boolean' },\n} as const;\n\n/** Every flag name, as a type. A verb cannot claim a flag that is not in the table. */\nexport type FlagName = keyof typeof CLI_FLAGS;\n\n/**\n * The flags answered *before* any verb, so no verb owns them and no verb may refuse\n * them: `smelt map --help` prints the help, exactly as it always has. Every other flag\n * belongs to at least one verb — `test/guards/subcommand-registry.test.ts` pins that.\n */\nexport const GLOBAL_FLAGS = ['help', 'version'] as const satisfies readonly FlagName[];\n\n/** A flag a verb can own — everything but the two global ones. */\nexport type VerbFlag = Exclude<FlagName, (typeof GLOBAL_FLAGS)[number]>;\n\n/** Every ownable flag, in table order: the list a refusal checks a verb against. */\nexport const VERB_FLAGS: readonly VerbFlag[] = Object.keys(CLI_FLAGS).filter(\n (name): name is VerbFlag => !(GLOBAL_FLAGS as readonly string[]).includes(name),\n);\n\n/**\n * `--a`, `--a and --b`, `--a, --b and --c` — in flag-table order, always.\n *\n * Every refusal that names more than one flag spells the list this way: the\n * ownership refusal in `./registry.ts`, and the default verb's `--reconstruct`\n * refusal in `./smelt.ts`. It lives with the table because the *order* is the\n * table's — a refusal that listed flags in the order the user happened to type them\n * would read differently every time, and two refusals disagreeing about how to spell\n * the same pair is the kind of drift this file exists to remove.\n */\nexport function flagList(flags: readonly VerbFlag[]): string {\n const spelled = VERB_FLAGS.filter((flag) => flags.includes(flag)).map((flag) => `--${flag}`);\n if (spelled.length <= 1) return spelled.join('');\n return `${spelled.slice(0, -1).join(', ')} and ${spelled.at(-1) ?? ''}`;\n}\n\n/** What one flag's parsed value looks like, derived from how `parseArgs` was told to read it. */\ntype FlagValue<F> = F extends { readonly type: 'boolean' }\n ? boolean\n : F extends { readonly multiple: true }\n ? readonly string[]\n : string;\n\n/**\n * The parsed flags, as every `Subcommand.parse` sees them: one optional field per\n * flag, typed by the table above. Absent means the user did not type it — which is\n * the only thing a refusal needs to know, and the only thing a verb may act on.\n */\nexport type FlagValues = { readonly [K in FlagName]?: FlagValue<(typeof CLI_FLAGS)[K]> };\n\n/** How one flag appears in the OPTIONS block of `--help`. */\nexport interface FlagHelp {\n /** The left column, e.g. `--budget <bytes>` or `-h, --help`. */\n readonly label: string;\n /**\n * The description, already wrapped to the OPTIONS column — a function because three\n * entries render a registry (`SUPPORTED_LANGUAGES`, `STRATEGIES` +\n * `STRUCTURAL_LANGUAGES`, `HARNESS_IDS`) rather than a hand-typed list. The\n * ownership prefix is not here; it is generated.\n */\n body(): readonly string[];\n}\n\n/**\n * A comma-separated list under an OPTIONS entry's hanging indent, wrapped where the\n * hand-typed version wrapped. `--strategy` and `--language` render their registries on\n * one line because they fit; the harness ids do not, and a list long enough to wrap is\n * exactly the list nobody keeps in sync by hand.\n */\nexport function optionList(items: readonly string[], width: number): readonly string[] {\n const lines: string[] = [];\n let line = '';\n items.forEach((item, index) => {\n const word = index === items.length - 1 ? `${item}.` : `${item},`;\n const candidate = line === '' ? word : `${line} ${word}`;\n if (line !== '' && candidate.length > width) {\n lines.push(line);\n line = word;\n } else {\n line = candidate;\n }\n });\n lines.push(line);\n return lines;\n}\n\n/** The width an OPTIONS description wraps within, once its 23-column indent is removed. */\nconst OPTION_BODY_WIDTH = 65;\n\n/**\n * The OPTIONS entry for every flag. `Record<FlagName, FlagHelp>` on purpose: a flag\n * added to {@link CLI_FLAGS} without a help entry is a compile error, so the help text\n * cannot fall behind what the parser accepts.\n */\nexport const FLAG_HELP: Readonly<Record<FlagName, FlagHelp>> = {\n budget: {\n label: '--budget <bytes>',\n body: () => [\n 'Required, unless smelt.config.json sets defaultBudgetBytes.',\n 'Soft ceiling for the output, in UTF-8 bytes (for map: a hard',\n 'ceiling, met by construction). No built-in default: a budget',\n `${CLI_NAME} invented would decide for you.`,\n ],\n },\n focus: {\n label: '--focus <term>',\n body: () => [\n 'What you were looking for. Repeatable. Matching regions and',\n 'their context survive; the runs between them collapse. For',\n 'map: symbols matching a term (by name or path) are promoted',\n 'to the front of the fill order, ranks unchanged.',\n ],\n },\n language: {\n label: '--language <id>',\n body: () => [`Override detection. One of: ${[...SUPPORTED_LANGUAGES, 'unknown'].join(', ')}.`],\n },\n strategy: {\n label: '--strategy <id>',\n body: () => [\n `${STRATEGIES.join(', ')}. Defaults to ${DEFAULT_STRATEGY}, unless`,\n 'smelt.config.json says otherwise. structural parses',\n `${STRUCTURAL_LANGUAGES.join(', ')};`,\n 'any other language is refused, never approximated. auto picks',\n 'structural for those languages and lexical for the rest, and',\n 'the report names whichever one actually ran.',\n ],\n },\n ignore: {\n label: '--ignore <entry>',\n body: () => [\n 'Repeatable. Replaces the default ignore list',\n // Read off DEFAULT_REPO_IGNORE, never re-typed: help text that lists a default\n // by hand is help text that will one day describe a different default.\n `(${DEFAULT_REPO_IGNORE.join(', ')}): a bare name matches any path segment,`,\n 'an entry containing / is a root-relative prefix.',\n ],\n },\n cache: {\n label: '--cache <dir>',\n body: () => [\n 'Directory for the tags cache, keyed by content',\n 'hash. Only when given does the map write to disk at all.',\n ],\n },\n harness: {\n label: '--harness <id>',\n body: () => [\n 'Skip harness detection and target one id:',\n ...optionList(HARNESS_IDS, OPTION_BODY_WIDTH),\n ],\n },\n strict: {\n label: '--strict',\n body: () => [\n 'Turn any lint finding into exit 1, for CI. Findings are',\n 'advisory by default: the rules are heuristics about somebody',\n \"else's house style, and enforcing them uninvited would be\",\n `${CLI_NAME} deciding it. A budget you set yourself is different —`,\n 'exceeding agents.budgetBytes exits 1 with or without --strict.',\n ],\n },\n json: {\n label: '--json',\n body: () => [\n 'Print a JSON envelope on stdout instead of the text:',\n '{ format, result, elided } for a smelt run — `result` is',\n 'the SmeltResult verbatim, `elided` carries the bytes, so',\n 'the envelope can be reconstructed; feed it back with',\n '--reconstruct. For map: { format, map }, the RepoMap',\n 'structure verbatim. For agents lint: { format, report },',\n 'the measured levels and every finding with its rule id.',\n ],\n },\n reconstruct: {\n label: '--reconstruct',\n body: () => [\n 'Read a --json envelope and print the original text, byte for',\n 'byte. This is Law 3 you can run from a shell.',\n ],\n },\n help: { label: '-h, --help', body: () => ['This text.'] },\n version: { label: '--version', body: () => ['The package version.'] },\n};\n\n/**\n * `--budget` has no built-in default, for the same reason `smelt()` has none: a budget\n * smelt invented would be smelt deciding how much of the caller's context to throw\n * away, silently, at a number nobody chose. A *missing* flag is not an error here,\n * though — `smelt.config.json` may carry a `defaultBudgetBytes` the user chose\n * explicitly, and the verb's `resolve` errors only when neither exists. A malformed\n * value is always an error.\n *\n * It lives with the flag rather than with a verb because two verbs own `--budget`, and\n * the two of them agreeing on what \"4kb\" means is not something to leave to chance.\n *\n * The *lexing* is the CLI's own and stays here — argv carries strings, so `4kb` and a\n * leading `-` are answered by a digits-only test before anything numeric happens. The\n * *rule* and *the sentence that refuses it* come from `ops/inputs.ts`, which is also\n * where the `smelt_file` tool gets them: two surfaces, one law, spelled `--budget`\n * here and `\"budgetBytes\"` there.\n */\nexport function parseBudget(raw: string | undefined): number | undefined {\n if (raw === undefined) return undefined;\n if (!/^\\d+$/.test(raw)) throw refuseBudget('not-an-integer', raw);\n const value = Number(raw);\n const fault = budgetFault(value);\n if (fault !== undefined) throw refuseBudget(fault, raw);\n return value;\n}\n\n/** The malformed-budget refusal, in the CLI's currency: prefixed, and exit 2. */\nfunction refuseBudget(fault: BudgetFault, raw: string): CliUsageError {\n return new CliUsageError(`${CLI_NAME}: ${budgetMalformed(fault, '--budget', raw)}`);\n}\n"]}
|
|
@@ -35,6 +35,7 @@ export const CLI_FLAGS = {
|
|
|
35
35
|
ignore: { type: 'string', multiple: true },
|
|
36
36
|
cache: { type: 'string' },
|
|
37
37
|
harness: { type: 'string' },
|
|
38
|
+
strict: { type: 'boolean' },
|
|
38
39
|
json: { type: 'boolean' },
|
|
39
40
|
reconstruct: { type: 'boolean' },
|
|
40
41
|
help: { type: 'boolean', short: 'h' },
|
|
@@ -152,6 +153,16 @@ export const FLAG_HELP = {
|
|
|
152
153
|
...optionList(HARNESS_IDS, OPTION_BODY_WIDTH),
|
|
153
154
|
],
|
|
154
155
|
},
|
|
156
|
+
strict: {
|
|
157
|
+
label: '--strict',
|
|
158
|
+
body: () => [
|
|
159
|
+
'Turn any lint finding into exit 1, for CI. Findings are',
|
|
160
|
+
'advisory by default: the rules are heuristics about somebody',
|
|
161
|
+
"else's house style, and enforcing them uninvited would be",
|
|
162
|
+
`${CLI_NAME} deciding it. A budget you set yourself is different —`,
|
|
163
|
+
'exceeding agents.budgetBytes exits 1 with or without --strict.',
|
|
164
|
+
],
|
|
165
|
+
},
|
|
155
166
|
json: {
|
|
156
167
|
label: '--json',
|
|
157
168
|
body: () => [
|
|
@@ -160,7 +171,8 @@ export const FLAG_HELP = {
|
|
|
160
171
|
'the SmeltResult verbatim, `elided` carries the bytes, so',
|
|
161
172
|
'the envelope can be reconstructed; feed it back with',
|
|
162
173
|
'--reconstruct. For map: { format, map }, the RepoMap',
|
|
163
|
-
'structure verbatim.',
|
|
174
|
+
'structure verbatim. For agents lint: { format, report },',
|
|
175
|
+
'the measured levels and every finding with its rule id.',
|
|
164
176
|
],
|
|
165
177
|
},
|
|
166
178
|
reconstruct: {
|