@shrkcrft/cli 0.1.0-alpha.23 → 0.1.0-alpha.25
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/commands/changelog-data.d.ts +25 -0
- package/dist/commands/changelog-data.d.ts.map +1 -0
- package/dist/commands/changelog-data.js +70 -0
- package/dist/commands/changelog.command.d.ts +3 -0
- package/dist/commands/changelog.command.d.ts.map +1 -0
- package/dist/commands/changelog.command.js +100 -0
- package/dist/commands/changes.command.d.ts.map +1 -1
- package/dist/commands/changes.command.js +4 -0
- package/dist/commands/check.command.d.ts.map +1 -1
- package/dist/commands/check.command.js +182 -17
- package/dist/commands/command-catalog.d.ts +11 -0
- package/dist/commands/command-catalog.d.ts.map +1 -1
- package/dist/commands/command-catalog.js +99 -0
- package/dist/commands/compress.command.d.ts.map +1 -1
- package/dist/commands/compress.command.js +15 -1
- package/dist/commands/constructs.command.d.ts.map +1 -1
- package/dist/commands/constructs.command.js +49 -14
- package/dist/commands/context.command.d.ts.map +1 -1
- package/dist/commands/context.command.js +31 -19
- package/dist/commands/finish.command.d.ts +3 -0
- package/dist/commands/finish.command.d.ts.map +1 -0
- package/dist/commands/finish.command.js +70 -0
- package/dist/commands/gate.command.d.ts.map +1 -1
- package/dist/commands/gate.command.js +6 -1
- package/dist/commands/gen.command.d.ts.map +1 -1
- package/dist/commands/gen.command.js +65 -9
- package/dist/commands/graph-code-subverbs.d.ts.map +1 -1
- package/dist/commands/graph-code-subverbs.js +56 -25
- package/dist/commands/help.command.d.ts.map +1 -1
- package/dist/commands/help.command.js +20 -2
- package/dist/commands/impact.command.d.ts.map +1 -1
- package/dist/commands/impact.command.js +17 -22
- package/dist/commands/policy-lint.command.d.ts.map +1 -1
- package/dist/commands/policy-lint.command.js +46 -8
- package/dist/commands/registry.command.d.ts.map +1 -1
- package/dist/commands/registry.command.js +71 -13
- package/dist/commands/reuse.command.d.ts.map +1 -1
- package/dist/commands/reuse.command.js +80 -14
- package/dist/commands/smart-context.command.d.ts.map +1 -1
- package/dist/commands/smart-context.command.js +34 -42
- package/dist/commands/task.command.d.ts.map +1 -1
- package/dist/commands/task.command.js +33 -16
- package/dist/commands/trace.command.d.ts.map +1 -1
- package/dist/commands/trace.command.js +73 -3
- package/dist/commands/wiring.command.d.ts +12 -0
- package/dist/commands/wiring.command.d.ts.map +1 -0
- package/dist/commands/wiring.command.js +384 -0
- package/dist/diff/collect-changed-paths.d.ts +5 -3
- package/dist/diff/collect-changed-paths.d.ts.map +1 -1
- package/dist/diff/collect-changed-paths.js +73 -36
- package/dist/diff/deleted-orphans.d.ts +42 -0
- package/dist/diff/deleted-orphans.d.ts.map +1 -0
- package/dist/diff/deleted-orphans.js +46 -0
- package/dist/finish/run-finish.d.ts +62 -0
- package/dist/finish/run-finish.d.ts.map +1 -0
- package/dist/finish/run-finish.js +239 -0
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +6 -0
- package/dist/validation/typecheck-emitted.d.ts +36 -0
- package/dist/validation/typecheck-emitted.d.ts.map +1 -0
- package/dist/validation/typecheck-emitted.js +109 -0
- package/package.json +33 -33
|
@@ -390,6 +390,14 @@ export const COMMAND_CATALOG = Object.freeze([
|
|
|
390
390
|
surface: CommandSurface.Common,
|
|
391
391
|
taskRole: CommandTaskRole.Validate,
|
|
392
392
|
}),
|
|
393
|
+
entry({
|
|
394
|
+
command: 'check orphans',
|
|
395
|
+
description: 'Write-safety guard: after deleting file(s)/export(s), finds surviving files that still import them or reference a symbol they declared — alias-resolved, incl. barrel re-exports the type checker misses. Reverse-closure over the diff vs --since (or --staged) against the code-graph snapshot; each survivor reported with file:line. [--since <ref>] [--staged] [--json]',
|
|
396
|
+
category: 'core',
|
|
397
|
+
safetyLevel: SafetyLevel.ReadOnly,
|
|
398
|
+
surface: CommandSurface.Common,
|
|
399
|
+
taskRole: CommandTaskRole.Validate,
|
|
400
|
+
}),
|
|
393
401
|
entry({
|
|
394
402
|
command: 'policy-lint',
|
|
395
403
|
description: 'Lint template/markup, stylesheet, and AOT-invisible TS surfaces against config-defined policyRules[] — sees `.html` files AND inline `template:` strings that tsc/AOT cannot. Deterministic; no AI. [--surface template|style|ts] [--changed-only] [--only <ids>] [--json]',
|
|
@@ -398,6 +406,46 @@ export const COMMAND_CATALOG = Object.freeze([
|
|
|
398
406
|
surface: CommandSurface.Common,
|
|
399
407
|
taskRole: CommandTaskRole.Validate,
|
|
400
408
|
}),
|
|
409
|
+
entry({
|
|
410
|
+
command: 'wiring explain',
|
|
411
|
+
description: 'Dry-run ONE configured wiringRule and print the declared set + registered set it extracts (token + file:line), the alias-resolved set-difference, and the verdict — the author-loop view of what `check wiring` sees, without re-running the gate. [--json]',
|
|
412
|
+
category: 'core',
|
|
413
|
+
safetyLevel: SafetyLevel.ReadOnly,
|
|
414
|
+
surface: CommandSurface.Advanced,
|
|
415
|
+
taskRole: CommandTaskRole.Explain,
|
|
416
|
+
}),
|
|
417
|
+
entry({
|
|
418
|
+
command: 'wiring test',
|
|
419
|
+
description: 'Dry-run an EPHEMERAL candidate wiring rule (a .json file or inline JSON) against the live tree — see the declared/registered sets + diff it would produce before committing it to sharkcraft.config.ts. Never writes config. [--json]',
|
|
420
|
+
category: 'core',
|
|
421
|
+
safetyLevel: SafetyLevel.ReadOnly,
|
|
422
|
+
surface: CommandSurface.Advanced,
|
|
423
|
+
taskRole: CommandTaskRole.Explain,
|
|
424
|
+
}),
|
|
425
|
+
entry({
|
|
426
|
+
command: 'wiring chain',
|
|
427
|
+
description: 'Registration/DI graph query: the declared → provided → consumed chain of a token across files/layers, alias-resolved, with file:line at each hop + a verdict (wired / unprovided / orphan). Models runtime wiring imports can\'t see. Idioms from sharkcraft.config.ts registrationGraph[]. [--json]',
|
|
428
|
+
category: 'analysis',
|
|
429
|
+
safetyLevel: SafetyLevel.ReadOnly,
|
|
430
|
+
surface: CommandSurface.Advanced,
|
|
431
|
+
taskRole: CommandTaskRole.Inspect,
|
|
432
|
+
}),
|
|
433
|
+
entry({
|
|
434
|
+
command: 'wiring unprovided',
|
|
435
|
+
description: 'Registration/DI graph query: tokens DECLARED or INJECTED but never PROVIDED — the silent-at-runtime class (typecheck/AOT-green, resolves to undefined at runtime). The write-safety check imports/grep structurally can\'t do. [--json]',
|
|
436
|
+
category: 'analysis',
|
|
437
|
+
safetyLevel: SafetyLevel.ReadOnly,
|
|
438
|
+
surface: CommandSurface.Advanced,
|
|
439
|
+
taskRole: CommandTaskRole.Validate,
|
|
440
|
+
}),
|
|
441
|
+
entry({
|
|
442
|
+
command: 'wiring orphans',
|
|
443
|
+
description: 'Registration/DI graph query: tokens PROVIDED/registered that nothing CONSUMES — a build-clean dead registration (or a sign the consumer was renamed/removed). [--json]',
|
|
444
|
+
category: 'analysis',
|
|
445
|
+
safetyLevel: SafetyLevel.ReadOnly,
|
|
446
|
+
surface: CommandSurface.Advanced,
|
|
447
|
+
taskRole: CommandTaskRole.Inspect,
|
|
448
|
+
}),
|
|
401
449
|
entry({
|
|
402
450
|
command: 'reuse',
|
|
403
451
|
description: 'Intent → the canonical primitive to reuse: matches your intent against config reusePrimitives[] then resolves the symbol through the code graph (transitive star-barrels) to its import path, sibling exports, and real consumer files to copy. Read-only; no AI.',
|
|
@@ -415,6 +463,14 @@ export const COMMAND_CATALOG = Object.freeze([
|
|
|
415
463
|
surface: CommandSurface.Common,
|
|
416
464
|
taskRole: CommandTaskRole.Validate,
|
|
417
465
|
}),
|
|
466
|
+
entry({
|
|
467
|
+
command: 'finish',
|
|
468
|
+
description: 'Composite "is this changeset safe to finish?" gate: EXECUTES boundaries + import-hygiene + wiring + policy + deleted-orphans changed-only inline, plus an impact summary, and returns ONE pass/fail. The trustworthy "done?" call after editing — superset of diff-check; honors 0-rules→skipped. Read-only. [files… | --staged | --since <ref>] [--json]',
|
|
469
|
+
category: 'core',
|
|
470
|
+
safetyLevel: SafetyLevel.ReadOnly,
|
|
471
|
+
surface: CommandSurface.Common,
|
|
472
|
+
taskRole: CommandTaskRole.Validate,
|
|
473
|
+
}),
|
|
418
474
|
entry({
|
|
419
475
|
command: 'review',
|
|
420
476
|
description: 'PR-review packet — changed files, affected rules, missing tests heuristic.',
|
|
@@ -1104,6 +1160,14 @@ export const COMMAND_CATALOG = Object.freeze([
|
|
|
1104
1160
|
safetyLevel: SafetyLevel.ReadOnly,
|
|
1105
1161
|
surface: CommandSurface.Common,
|
|
1106
1162
|
}),
|
|
1163
|
+
entry({
|
|
1164
|
+
command: 'changelog',
|
|
1165
|
+
description: 'The shrk command-surface delta of the running build (added / changed / removed verbs & flags). `--since <version>` for the cumulative delta; offline, authoritative for this exact build.',
|
|
1166
|
+
category: 'meta',
|
|
1167
|
+
safetyLevel: SafetyLevel.ReadOnly,
|
|
1168
|
+
surface: CommandSurface.Common,
|
|
1169
|
+
intendedAudience: [CommandAudience.Human, CommandAudience.Agent],
|
|
1170
|
+
}),
|
|
1107
1171
|
entry({
|
|
1108
1172
|
command: 'completion',
|
|
1109
1173
|
description: 'Print a sourcable shell-completion script for the `shrk` CLI (bash | zsh | fish). Pipe into your shell rc.',
|
|
@@ -1658,6 +1722,14 @@ export const COMMAND_CATALOG = Object.freeze([
|
|
|
1658
1722
|
safetyLevel: SafetyLevel.ReadOnly,
|
|
1659
1723
|
mcpAvailable: true,
|
|
1660
1724
|
}),
|
|
1725
|
+
entry({
|
|
1726
|
+
command: 'trace literal',
|
|
1727
|
+
description: 'Trace an EXACT string literal across the codebase, classified by direction: declare → register → consume sites of a cross-fence contract (kind slug, permission id, route key) the type system can\'t link and grep can\'t classify. Resolves `const X = "lit"` aliases. Generalizes `registry … where` to any literal — no pre-declared registry. [--glob <g>] [--no-aliases] [--limit N] [--json]',
|
|
1728
|
+
category: 'analysis',
|
|
1729
|
+
safetyLevel: SafetyLevel.ReadOnly,
|
|
1730
|
+
surface: CommandSurface.Advanced,
|
|
1731
|
+
taskRole: CommandTaskRole.Inspect,
|
|
1732
|
+
}),
|
|
1661
1733
|
entry({
|
|
1662
1734
|
command: 'feedback',
|
|
1663
1735
|
description: 'Feedback ingestion (ingest|summarize|actions|convert-to-backlog). Read-only.',
|
|
@@ -3664,6 +3736,7 @@ const PRIMARY_VERBS_ALLOWLIST = new Set([
|
|
|
3664
3736
|
'gen',
|
|
3665
3737
|
'apply',
|
|
3666
3738
|
'check',
|
|
3739
|
+
'finish',
|
|
3667
3740
|
'quality',
|
|
3668
3741
|
'plan',
|
|
3669
3742
|
'fix',
|
|
@@ -3734,6 +3807,32 @@ export function defaultShowInHelp(e) {
|
|
|
3734
3807
|
const surface = commandSurface(e);
|
|
3735
3808
|
return surface === CommandSurface.Primary || surface === CommandSurface.Common;
|
|
3736
3809
|
}
|
|
3810
|
+
/**
|
|
3811
|
+
* The "explain / dry-run" command family: entries that show you what a gate,
|
|
3812
|
+
* ranker, or graph *sees* before you act (`search tuning explain`, `wiring
|
|
3813
|
+
* explain`/`wiring test`, `boundaries explain`, `surface explain`, …). Several
|
|
3814
|
+
* carry an Advanced surface, so {@link defaultShowInHelp} hides them despite
|
|
3815
|
+
* being high-value — which is exactly why an agent finds them only by guessing.
|
|
3816
|
+
* `shrk --full-help` lists this family in a dedicated section so they stop
|
|
3817
|
+
* being hidden. Returns callable entries only (retired/deprecated/alias and
|
|
3818
|
+
* R46-pruned are excluded), sorted by command.
|
|
3819
|
+
*/
|
|
3820
|
+
export function listExplainFamily() {
|
|
3821
|
+
return COMMAND_CATALOG.filter((e) => {
|
|
3822
|
+
const lc = commandLifecycle(e);
|
|
3823
|
+
if (lc === CommandLifecycle.Deprecated ||
|
|
3824
|
+
lc === CommandLifecycle.Retired ||
|
|
3825
|
+
lc === CommandLifecycle.Alias) {
|
|
3826
|
+
return false;
|
|
3827
|
+
}
|
|
3828
|
+
if (R46_OVERLAY[e.command])
|
|
3829
|
+
return false;
|
|
3830
|
+
const role = commandTaskRole(e);
|
|
3831
|
+
return role === CommandTaskRole.Explain || /(^|\s)explain$/.test(e.command);
|
|
3832
|
+
})
|
|
3833
|
+
.slice()
|
|
3834
|
+
.sort((a, b) => a.command.localeCompare(b.command));
|
|
3835
|
+
}
|
|
3737
3836
|
/**
|
|
3738
3837
|
* Short "Use this when…" line derived from `surface`, `taskRole`,
|
|
3739
3838
|
* `preferredCommand`, `replacedBy`, and `machineOnly`. Empty string when
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compress.command.d.ts","sourceRoot":"","sources":["../../src/commands/compress.command.ts"],"names":[],"mappings":"AAQA,OAAO,EAKL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAqEhC,eAAO,MAAM,eAAe,EAAE,
|
|
1
|
+
{"version":3,"file":"compress.command.d.ts","sourceRoot":"","sources":["../../src/commands/compress.command.ts"],"names":[],"mappings":"AAQA,OAAO,EAKL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAqEhC,eAAO,MAAM,eAAe,EAAE,eAqH7B,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,aAAa,EAAE,eA4B3B,CAAC"}
|
|
@@ -60,7 +60,7 @@ const COMPRESS_BOOLEAN_FLAGS = new Set([
|
|
|
60
60
|
]);
|
|
61
61
|
export const compressCommand = {
|
|
62
62
|
name: 'compress',
|
|
63
|
-
description: 'Compress a blob (file or stdin) deterministically to cut tokens — JSON→table, logs/search/diffs→signal. Reversible via `shrk expand`.',
|
|
63
|
+
description: 'Compress a blob (file or stdin) deterministically to cut tokens — JSON→table, logs/search/diffs→signal. Reversible via `shrk expand`. NOTE: `--type code` emits a LOSSY outline (function bodies elided) — good for fitting more code into context, NOT for line-accurate reading; use plain Read (or `--lossless`) to inspect a file.',
|
|
64
64
|
usage: 'shrk [--cwd <dir>] compress [<file>|-] [--stdin] [--type <content-type>] [--query <text>] [--max <n>] [--lossless] [--no-cache] [--json]',
|
|
65
65
|
booleanFlags: COMPRESS_BOOLEAN_FLAGS,
|
|
66
66
|
run(args) {
|
|
@@ -118,6 +118,13 @@ export const compressCommand = {
|
|
|
118
118
|
tokensSaved: result.savings.saved,
|
|
119
119
|
savedRatio: result.savings.ratio,
|
|
120
120
|
tokensAreEstimated: true,
|
|
121
|
+
// Explicit fidelity signal so a caller never mistakes a lossy code
|
|
122
|
+
// outline for a line-accurate read.
|
|
123
|
+
fidelity: result.strategy === ECompressionStrategy.Code
|
|
124
|
+
? 'lossy-outline (not line-accurate; use Read or --lossless to inspect)'
|
|
125
|
+
: result.lossy
|
|
126
|
+
? 'lossy'
|
|
127
|
+
: 'lossless',
|
|
121
128
|
queryApplied,
|
|
122
129
|
ccrKey: result.ccrKey ?? null,
|
|
123
130
|
note: result.note,
|
|
@@ -136,6 +143,13 @@ export const compressCommand = {
|
|
|
136
143
|
process.stdout.write(result.compressed + '\n');
|
|
137
144
|
const cached = result.ccrKey ? ` · original cached as ${result.ccrKey} (shrk expand ${result.ccrKey})` : '';
|
|
138
145
|
process.stderr.write(`${result.strategy}: ~${result.savings.before} → ~${result.savings.after} tokens (−${pct}%, est.)${cached}\n`);
|
|
146
|
+
// Fidelity banner: the code outline elides function bodies, so it is a
|
|
147
|
+
// footgun as a cheaper Read. Say so loudly — same failure mode as a lossy
|
|
148
|
+
// result that doesn't *look* lossy.
|
|
149
|
+
if (result.strategy === ECompressionStrategy.Code) {
|
|
150
|
+
process.stderr.write('note: code outline is LOSSY (bodies elided) — for bulk context, not line-accurate reading; ' +
|
|
151
|
+
'use `Read` or `--lossless` for inspection.\n');
|
|
152
|
+
}
|
|
139
153
|
// Token-economy guard: when auto-detect declines to compress a non-trivial
|
|
140
154
|
// input, a silent `−0%` re-emit looks like success. Nudge toward `--type`
|
|
141
155
|
// (stdout stays the verbatim blob; exit code unchanged).
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constructs.command.d.ts","sourceRoot":"","sources":["../../src/commands/constructs.command.ts"],"names":[],"mappings":"AA8BA,OAAO,EAKL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"constructs.command.d.ts","sourceRoot":"","sources":["../../src/commands/constructs.command.ts"],"names":[],"mappings":"AA8BA,OAAO,EAKL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAkKhC,eAAO,MAAM,qBAAqB,EAAE,eAqBnC,CAAC;AAEF,eAAO,MAAM,oBAAoB,EAAE,eAmDlC,CAAC;AAEF,eAAO,MAAM,sBAAsB,EAAE,eAgGpC,CAAC;AAEF,eAAO,MAAM,uBAAuB,EAAE,eAyErC,CAAC;AAEF,eAAO,MAAM,wBAAwB,EAAE,eAkCtC,CAAC;AAEF,eAAO,MAAM,sBAAsB,EAAE,eA2BpC,CAAC;AAEF,eAAO,MAAM,oBAAoB,EAAE,eAiClC,CAAC;AAEF,eAAO,MAAM,uBAAuB,EAAE,eAiBrC,CAAC;AAEF,eAAO,MAAM,uBAAuB,EAAE,eAkBrC,CAAC;AAEF,eAAO,MAAM,uBAAuB,EAAE,eA2BrC,CAAC;AAEF,eAAO,MAAM,uBAAuB,EAAE,eA8BrC,CAAC;AAEF,eAAO,MAAM,sBAAsB,EAAE,eA0DpC,CAAC;AAEF,eAAO,MAAM,sBAAsB,EAAE,eA0LpC,CAAC"}
|
|
@@ -4,6 +4,48 @@ import { AdoptionCheckpointStatus, buildConstructAdoptionDiff, buildConstructAdo
|
|
|
4
4
|
import { GraphQueryApi, GraphStore } from '@shrkcrft/graph';
|
|
5
5
|
import { flagBool, flagNumber, flagString, resolveCwd, } from "../command-registry.js";
|
|
6
6
|
import { asJson, header } from "../output/format-output.js";
|
|
7
|
+
/**
|
|
8
|
+
* A construct subverb was handed an id that is NOT a registered construct.
|
|
9
|
+
* `constructs *` matches a curated construct-registry ID, not a raw code symbol
|
|
10
|
+
* — so a miss must never read as an existence claim about the symbol. Probe the
|
|
11
|
+
* code graph and redirect to the tools that actually answer "where does X live /
|
|
12
|
+
* who calls it" instead of a definitive-sounding "No construct". Returns exit 1.
|
|
13
|
+
*/
|
|
14
|
+
function emitConstructMiss(id, args) {
|
|
15
|
+
const cwd = resolveCwd(args);
|
|
16
|
+
const store = new GraphStore(cwd);
|
|
17
|
+
let isCodeSymbol = false;
|
|
18
|
+
if (store.exists()) {
|
|
19
|
+
const api = GraphQueryApi.fromStore(cwd);
|
|
20
|
+
isCodeSymbol =
|
|
21
|
+
api.findSymbol(id, { exact: true }).length > 0 ||
|
|
22
|
+
api.findSymbol(id, { exact: false, limit: 1 }).length > 0;
|
|
23
|
+
}
|
|
24
|
+
if (flagBool(args, 'json')) {
|
|
25
|
+
process.stdout.write(asJson({
|
|
26
|
+
schema: 'sharkcraft.constructs-miss/v1',
|
|
27
|
+
id,
|
|
28
|
+
found: false,
|
|
29
|
+
reason: 'not-a-construct-id',
|
|
30
|
+
isCodeSymbol,
|
|
31
|
+
hint: isCodeSymbol
|
|
32
|
+
? `"${id}" is a code symbol, not a construct — try 'shrk graph callers ${id}' or 'shrk graph search ${id}'.`
|
|
33
|
+
: `"${id}" is not a registered construct ID; list them with 'shrk constructs list', or search code with 'shrk graph search ${id}'.`,
|
|
34
|
+
}) + '\n');
|
|
35
|
+
return 1;
|
|
36
|
+
}
|
|
37
|
+
if (isCodeSymbol) {
|
|
38
|
+
process.stderr.write(`"${id}" is not a construct ID — did you mean a code symbol?\n` +
|
|
39
|
+
` shrk graph callers ${id} # who references it (path:line)\n` +
|
|
40
|
+
` shrk graph search ${id} # find its declaration\n`);
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
process.stderr.write(`"${id}" is not a registered construct ID, and no code symbol by that name was found.\n` +
|
|
44
|
+
` shrk constructs list # the curated construct IDs\n` +
|
|
45
|
+
` shrk graph search ${id} # search the code graph\n`);
|
|
46
|
+
}
|
|
47
|
+
return 1;
|
|
48
|
+
}
|
|
7
49
|
async function loadAll(args) {
|
|
8
50
|
const cwd = resolveCwd(args);
|
|
9
51
|
const inspection = await inspectSharkcraft({ cwd });
|
|
@@ -116,8 +158,7 @@ export const constructsGetCommand = {
|
|
|
116
158
|
const { constructs } = await loadAll(args);
|
|
117
159
|
const c = constructs.find((x) => x.id === id);
|
|
118
160
|
if (!c) {
|
|
119
|
-
|
|
120
|
-
return 1;
|
|
161
|
+
return emitConstructMiss(id, args);
|
|
121
162
|
}
|
|
122
163
|
if (flagBool(args, 'json')) {
|
|
123
164
|
process.stdout.write(asJson(c) + '\n');
|
|
@@ -175,8 +216,7 @@ export const constructsTraceCommand = {
|
|
|
175
216
|
const { constructs } = await loadAll(args);
|
|
176
217
|
const c = constructs.find((x) => x.id === id);
|
|
177
218
|
if (!c) {
|
|
178
|
-
|
|
179
|
-
return 1;
|
|
219
|
+
return emitConstructMiss(id, args);
|
|
180
220
|
}
|
|
181
221
|
const trace = traceConstruct(c);
|
|
182
222
|
const deep = flagBool(args, 'deep');
|
|
@@ -274,8 +314,7 @@ export const constructsImpactCommand = {
|
|
|
274
314
|
const { constructs, inspection } = await loadAll(args);
|
|
275
315
|
const c = constructs.find((x) => x.id === id);
|
|
276
316
|
if (!c) {
|
|
277
|
-
|
|
278
|
-
return 1;
|
|
317
|
+
return emitConstructMiss(id, args);
|
|
279
318
|
}
|
|
280
319
|
const trace = traceConstruct(c);
|
|
281
320
|
/**
|
|
@@ -352,8 +391,7 @@ export const constructsRelatedCommand = {
|
|
|
352
391
|
const { constructs } = await loadAll(args);
|
|
353
392
|
const c = constructs.find((x) => x.id === id);
|
|
354
393
|
if (!c) {
|
|
355
|
-
|
|
356
|
-
return 1;
|
|
394
|
+
return emitConstructMiss(id, args);
|
|
357
395
|
}
|
|
358
396
|
const related = [
|
|
359
397
|
...(c.relatedKnowledge ?? []).map((id) => ({ kind: 'knowledge', id })),
|
|
@@ -389,8 +427,7 @@ export const constructsFilesCommand = {
|
|
|
389
427
|
const { constructs } = await loadAll(args);
|
|
390
428
|
const c = constructs.find((x) => x.id === id);
|
|
391
429
|
if (!c) {
|
|
392
|
-
|
|
393
|
-
return 1;
|
|
430
|
+
return emitConstructMiss(id, args);
|
|
394
431
|
}
|
|
395
432
|
const trace = traceConstruct(c);
|
|
396
433
|
// Emit the graph-resolved files (globs expanded), not the raw declared
|
|
@@ -419,8 +456,7 @@ export const constructsApiCommand = {
|
|
|
419
456
|
const { constructs } = await loadAll(args);
|
|
420
457
|
const c = constructs.find((x) => x.id === id);
|
|
421
458
|
if (!c) {
|
|
422
|
-
|
|
423
|
-
return 1;
|
|
459
|
+
return emitConstructMiss(id, args);
|
|
424
460
|
}
|
|
425
461
|
const publicApi = c.publicApi ?? [];
|
|
426
462
|
if (flagBool(args, 'json')) {
|
|
@@ -493,8 +529,7 @@ export const constructsFacetsCommand = {
|
|
|
493
529
|
const { constructs } = await loadAll(args);
|
|
494
530
|
const c = constructs.find((x) => x.id === id);
|
|
495
531
|
if (!c) {
|
|
496
|
-
|
|
497
|
-
return 1;
|
|
532
|
+
return emitConstructMiss(id, args);
|
|
498
533
|
}
|
|
499
534
|
const facets = c.facets ?? {};
|
|
500
535
|
if (flagBool(args, 'json')) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.command.d.ts","sourceRoot":"","sources":["../../src/commands/context.command.ts"],"names":[],"mappings":"AAiBA,OAAO,EAML,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"context.command.d.ts","sourceRoot":"","sources":["../../src/commands/context.command.ts"],"names":[],"mappings":"AAiBA,OAAO,EAML,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AA0ChC,eAAO,MAAM,cAAc,EAAE,eA8I5B,CAAC"}
|
|
@@ -6,6 +6,15 @@ import * as nodePath from 'node:path';
|
|
|
6
6
|
import { buildUniversalSearch, explainTaskRouting, recommendCommands, renderOverviewText, buildProjectOverview, } from '@shrkcrft/inspector';
|
|
7
7
|
import { flagBool, flagNumber, flagString, flagList, resolveCwd, } from "../command-registry.js";
|
|
8
8
|
import { asJson, header } from "../output/format-output.js";
|
|
9
|
+
/**
|
|
10
|
+
* Budget used for the DEFAULT full human-text orientation view when the caller
|
|
11
|
+
* set no explicit `--max-tokens`. Orientation is the cheap-first step an agent
|
|
12
|
+
* uses to form its initial model of the project — it must not silently drop the
|
|
13
|
+
* body of the richest sections (architecture, conventions, paths, workflows) the
|
|
14
|
+
* way a tight budget would. Large enough to hold every planned section; explicit
|
|
15
|
+
* `--max-tokens` and JSON/`--summary` callers still get the configured budget.
|
|
16
|
+
*/
|
|
17
|
+
const WIDE_CONTEXT_BUDGET = 100_000;
|
|
9
18
|
/**
|
|
10
19
|
* Minimal JSON shape for agent / skill consumption — the context-side mirror
|
|
11
20
|
* of `shrk task --compact`. Drops the heavy `body` and `request` echo and
|
|
@@ -65,8 +74,19 @@ export const contextCommand = {
|
|
|
65
74
|
const noPaths = flagBool(args, 'no-paths');
|
|
66
75
|
const includeDocs = flagBool(args, 'include-docs');
|
|
67
76
|
const includeCommands = flagBool(args, 'include-commands');
|
|
77
|
+
// Orientation renders the FULL body by default now (parity with why / reuse
|
|
78
|
+
// / knowledge get). `--summary`/`--brief` opts back into the terse view.
|
|
79
|
+
const wantsSummary = flagBool(args, 'summary') || flagBool(args, 'brief');
|
|
80
|
+
const wantsJsonOut = flagBool(args, 'json') || flagBool(args, 'machine-json');
|
|
68
81
|
const inspection = await inspectSharkcraft({ cwd: resolveCwd(args) });
|
|
69
82
|
const overview = buildProjectOverview(inspection.workspace, inspection.config?.projectName);
|
|
83
|
+
// Auto-widen the budget for the default full human view so no requested
|
|
84
|
+
// section is dropped purely to fit a tight budget. Explicit `--max-tokens`,
|
|
85
|
+
// JSON, and summary callers keep the configured budget.
|
|
86
|
+
const effectiveMaxTokens = maxTokens ??
|
|
87
|
+
(!wantsJsonOut && !wantsSummary
|
|
88
|
+
? WIDE_CONTEXT_BUDGET
|
|
89
|
+
: (inspection.config?.defaultMaxTokens ?? 3000));
|
|
70
90
|
const contextBoost = contextTuningBoostFor(inspection, task);
|
|
71
91
|
const result = buildContext(inspection.knowledgeEntries, {
|
|
72
92
|
task,
|
|
@@ -74,7 +94,7 @@ export const contextCommand = {
|
|
|
74
94
|
area,
|
|
75
95
|
tags,
|
|
76
96
|
scope,
|
|
77
|
-
maxTokens:
|
|
97
|
+
maxTokens: effectiveMaxTokens,
|
|
78
98
|
includeExamples: !noExamples,
|
|
79
99
|
includeTemplates: !noTemplates,
|
|
80
100
|
includeRules: !noRules,
|
|
@@ -84,14 +104,10 @@ export const contextCommand = {
|
|
|
84
104
|
projectOverview: renderOverviewText(overview),
|
|
85
105
|
...(contextBoost ? { boostFor: contextBoost } : {}),
|
|
86
106
|
});
|
|
87
|
-
// Surface top commands prominently before the long context body.
|
|
88
|
-
//
|
|
89
|
-
//
|
|
90
|
-
|
|
91
|
-
const actionVerbRe = /^(rename|add|fix|refactor|remove|delete|migrate|wire|explore|create|implement|update|introduce|build|extract|move|inline|generate|scaffold)\b/i;
|
|
92
|
-
const isActionLike = actionVerbRe.test(task.trim());
|
|
93
|
-
const wantsFull = flagBool(args, 'full');
|
|
94
|
-
const commandsFirst = flagBool(args, 'commands-first') || (isActionLike && !wantsFull);
|
|
107
|
+
// Surface top commands prominently before the long context body. The body
|
|
108
|
+
// itself now prints by default (parity with sibling orientation verbs);
|
|
109
|
+
// `--commands-first` keeps the terse commands-only view for action tasks.
|
|
110
|
+
const commandsOnly = flagBool(args, 'commands-first');
|
|
95
111
|
let commandRecommendations = null;
|
|
96
112
|
let routingMatches = [];
|
|
97
113
|
let searchReport = null;
|
|
@@ -141,16 +157,12 @@ export const contextCommand = {
|
|
|
141
157
|
process.stdout.write(` • ${m.hint.id} ${m.hint.title}\n`);
|
|
142
158
|
}
|
|
143
159
|
}
|
|
144
|
-
//
|
|
145
|
-
//
|
|
146
|
-
//
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
}
|
|
151
|
-
else if (isActionLike && !flagBool(args, 'commands-first')) {
|
|
152
|
-
process.stdout.write('\n(action-like task → commands-first; pass --full to see the long context body.)\n');
|
|
153
|
-
}
|
|
160
|
+
// The full context body prints by default now — parity with why / reuse /
|
|
161
|
+
// knowledge get, so the agent's first read of the project isn't thinner than
|
|
162
|
+
// every adjacent command. `--summary`/`--brief` (or `--commands-first`) opts
|
|
163
|
+
// back into the terse view.
|
|
164
|
+
if (wantsSummary || commandsOnly) {
|
|
165
|
+
process.stdout.write('\n(summary mode — omit --summary for the full context body, --json for machine output.)\n');
|
|
154
166
|
return 0;
|
|
155
167
|
}
|
|
156
168
|
process.stdout.write('\n');
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"finish.command.d.ts","sourceRoot":"","sources":["../../src/commands/finish.command.ts"],"names":[],"mappings":"AACA,OAAO,EAIL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AA8DhC,eAAO,MAAM,aAAa,EAAE,eAmB3B,CAAC"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { flagBool, flagString, resolveCwd, } from "../command-registry.js";
|
|
2
|
+
import { asJson, bullet, header, kv } from "../output/format-output.js";
|
|
3
|
+
import { runFinishGates } from "../finish/run-finish.js";
|
|
4
|
+
function resolveScope(args, cwd) {
|
|
5
|
+
const staged = flagBool(args, 'staged');
|
|
6
|
+
const since = flagString(args, 'since');
|
|
7
|
+
const filesRaw = flagString(args, 'files');
|
|
8
|
+
const files = filesRaw
|
|
9
|
+
? filesRaw.split(',').map((s) => s.trim()).filter((s) => s.length > 0)
|
|
10
|
+
: args.positional.filter((s) => s.length > 0);
|
|
11
|
+
if (files.length > 0)
|
|
12
|
+
return { mode: 'files', options: { projectRoot: cwd, files } };
|
|
13
|
+
if (staged)
|
|
14
|
+
return { mode: 'staged', options: { projectRoot: cwd, staged: true } };
|
|
15
|
+
if (since)
|
|
16
|
+
return { mode: 'since', options: { projectRoot: cwd, since } };
|
|
17
|
+
return { mode: 'worktree', options: { projectRoot: cwd, includeWorktree: true } };
|
|
18
|
+
}
|
|
19
|
+
const STATUS_GLYPH = {
|
|
20
|
+
pass: '✓',
|
|
21
|
+
fail: '✗',
|
|
22
|
+
skipped: '–',
|
|
23
|
+
};
|
|
24
|
+
function renderText(report) {
|
|
25
|
+
process.stdout.write(header('Finish — is this changeset safe to complete?'));
|
|
26
|
+
process.stdout.write(kv('scope', `${report.scope.mode} (${report.scope.fileCount} file${report.scope.fileCount === 1 ? '' : 's'})`) +
|
|
27
|
+
'\n');
|
|
28
|
+
for (const g of report.gates) {
|
|
29
|
+
process.stdout.write(` ${STATUS_GLYPH[g.status]} ${g.name.padEnd(11)} ${g.status.padEnd(8)} ${g.detail}\n`);
|
|
30
|
+
}
|
|
31
|
+
if (report.impact.ran) {
|
|
32
|
+
process.stdout.write(kv('impact', `risk=${report.impact.risk}, ${report.impact.directDependents} direct / ${report.impact.transitiveDependents} transitive dependents`) +
|
|
33
|
+
'\n');
|
|
34
|
+
}
|
|
35
|
+
else if (report.impact.note) {
|
|
36
|
+
process.stdout.write(kv('impact', `(skipped — ${report.impact.note})`) + '\n');
|
|
37
|
+
}
|
|
38
|
+
process.stdout.write(kv('verdict', report.verdict) + '\n\n');
|
|
39
|
+
process.stdout.write(report.summary + '\n');
|
|
40
|
+
const failing = report.gates.filter((g) => g.status === 'fail');
|
|
41
|
+
for (const g of failing) {
|
|
42
|
+
process.stdout.write(`\n${g.name} — failing items:\n`);
|
|
43
|
+
for (const item of g.items.slice(0, 15)) {
|
|
44
|
+
const loc = item.file ? `${item.file}${item.line ? `:${item.line}` : ''}` : '';
|
|
45
|
+
process.stdout.write(bullet(`${loc ? loc + ' — ' : ''}${item.message}`) + '\n');
|
|
46
|
+
}
|
|
47
|
+
if (g.items.length > 15) {
|
|
48
|
+
process.stdout.write(` … and ${g.items.length - 15} more (pass --json for the full list).\n`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
process.stdout.write(`\nNext: ${report.nextAction}\n`);
|
|
52
|
+
}
|
|
53
|
+
export const finishCommand = {
|
|
54
|
+
name: 'finish',
|
|
55
|
+
description: 'Composite "is this changeset safe to finish?" gate: EXECUTES every deterministic changed-only check inline — boundaries + import-hygiene + wiring + policy + deleted-orphans — plus an impact summary, and returns ONE pass/fail. The single trustworthy "done?" call after editing (superset of `diff-check`; honors 0-rules→skipped). Read-only.',
|
|
56
|
+
usage: 'shrk [--cwd <dir>] finish [files... | --files a.ts,b.ts | --staged | --since <ref>] [--json]',
|
|
57
|
+
booleanFlags: new Set(['json', 'staged']),
|
|
58
|
+
async run(args) {
|
|
59
|
+
const cwd = resolveCwd(args);
|
|
60
|
+
const wantJson = flagBool(args, 'json');
|
|
61
|
+
const { mode, options } = resolveScope(args, cwd);
|
|
62
|
+
const report = await runFinishGates({ cwd, mode, scope: options });
|
|
63
|
+
if (wantJson) {
|
|
64
|
+
process.stdout.write(asJson(report) + '\n');
|
|
65
|
+
return report.verdict === 'fail' ? 1 : 0;
|
|
66
|
+
}
|
|
67
|
+
renderText(report);
|
|
68
|
+
return report.verdict === 'fail' ? 1 : 0;
|
|
69
|
+
},
|
|
70
|
+
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gate.command.d.ts","sourceRoot":"","sources":["../../src/commands/gate.command.ts"],"names":[],"mappings":"AAaA,OAAO,EAIL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAGhC;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,WAAW,EAAE,
|
|
1
|
+
{"version":3,"file":"gate.command.d.ts","sourceRoot":"","sources":["../../src/commands/gate.command.ts"],"names":[],"mappings":"AAaA,OAAO,EAIL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAGhC;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,WAAW,EAAE,eAsMzB,CAAC"}
|
|
@@ -143,7 +143,12 @@ export const gateCommand = {
|
|
|
143
143
|
: {}),
|
|
144
144
|
impact: {
|
|
145
145
|
...(sinceRef ? { sinceRef } : {}),
|
|
146
|
-
|
|
146
|
+
// Blast-radius risk is inherently PRE-EXISTING structure (touching a hub
|
|
147
|
+
// is risky but not a new failure this change introduced), so the composite
|
|
148
|
+
// gate treats it as ADVISORY by default — `failOn: []` warns instead of
|
|
149
|
+
// redding, keeping the verdict change-attributable. `--fail-on critical`
|
|
150
|
+
// opts into a hard fail; `--strict` escalates the advisory warn.
|
|
151
|
+
failOn: failOn ?? [],
|
|
147
152
|
// Scope the impact gate to the changeset too: with `--since` we keep the
|
|
148
153
|
// gitref diff; with `--changed-only` / `--staged` / `--files` (and no
|
|
149
154
|
// `--since`) we analyze the resolved changed-file set directly.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gen.command.d.ts","sourceRoot":"","sources":["../../src/commands/gen.command.ts"],"names":[],"mappings":"AASA,OAAO,EAKL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"gen.command.d.ts","sourceRoot":"","sources":["../../src/commands/gen.command.ts"],"names":[],"mappings":"AASA,OAAO,EAKL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAuBhC,eAAO,MAAM,UAAU,EAAE,eAsNxB,CAAC"}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { inspectSharkcraft } from '@shrkcrft/inspector';
|
|
2
2
|
import { buildSavedPlan, FileChangeType, generate, OverwriteStrategy, savePlanToFile, signPlan, } from '@shrkcrft/generator';
|
|
3
3
|
import { flagBool, flagString, flagVars, resolveCwd, } from "../command-registry.js";
|
|
4
|
+
import * as nodePath from 'node:path';
|
|
4
5
|
import { asJson, header } from "../output/format-output.js";
|
|
5
6
|
import { printError } from "../output/print-error.js";
|
|
7
|
+
import { typecheckEmittedFiles, } from "../validation/typecheck-emitted.js";
|
|
6
8
|
const CHANGE_LABEL = {
|
|
7
9
|
[FileChangeType.Create]: 'CREATE',
|
|
8
10
|
[FileChangeType.Update]: 'UPDATE',
|
|
@@ -19,7 +21,7 @@ const CHANGE_LABEL = {
|
|
|
19
21
|
export const genCommand = {
|
|
20
22
|
name: 'gen',
|
|
21
23
|
description: 'Generate code from a template. Defaults to dry-run.',
|
|
22
|
-
usage: 'shrk gen <templateId> [<name>] [--var key=value ...] [--dry-run] [--write] [--force] [--save-plan <file>] [--show-content] [--json]',
|
|
24
|
+
usage: 'shrk gen <templateId> [<name>] [--var key=value ...] [--dry-run] [--write] [--force] [--save-plan <file>] [--print|--show-content] [--typecheck] [--json]\n (--typecheck compiles the emitted files against the detected tsconfig BEFORE apply — a template bug fails here, not at the human next build; --print shows the rendered bodies)',
|
|
23
25
|
async run(args) {
|
|
24
26
|
const templateId = args.positional[0];
|
|
25
27
|
const name = args.positional[1];
|
|
@@ -40,19 +42,50 @@ export const genCommand = {
|
|
|
40
42
|
: flagString(args, 'overwrite') ?? OverwriteStrategy.Never;
|
|
41
43
|
const variables = flagVars(args);
|
|
42
44
|
const savePlanPath = flagString(args, 'save-plan');
|
|
43
|
-
const
|
|
45
|
+
const wantTypecheck = flagBool(args, 'typecheck');
|
|
46
|
+
const genOpts = {
|
|
44
47
|
templateId: template.id,
|
|
45
48
|
name,
|
|
46
49
|
variables,
|
|
47
50
|
projectRoot: inspection.projectRoot,
|
|
48
51
|
overwriteStrategy: overwrite,
|
|
49
|
-
|
|
50
|
-
|
|
52
|
+
};
|
|
53
|
+
// --typecheck is a PRE-WRITE gate. Render a dry-run FIRST, compile the emitted
|
|
54
|
+
// (full-file create) TS/TSX against the detected tsconfig in memory, and only
|
|
55
|
+
// proceed to write if they compile — so an agent driving gen→apply never lands
|
|
56
|
+
// non-compiling code. On failure the write is refused (nothing touches disk).
|
|
57
|
+
// Only whole-file creates are checkable standalone (update ops are fragments).
|
|
58
|
+
let typecheckResult;
|
|
59
|
+
let effectiveWrite = write;
|
|
60
|
+
let result;
|
|
61
|
+
if (wantTypecheck) {
|
|
62
|
+
const dry = generate(template, { ...genOpts, write: false });
|
|
63
|
+
if (!dry.ok) {
|
|
64
|
+
printError(dry.error);
|
|
65
|
+
return 1;
|
|
66
|
+
}
|
|
67
|
+
const emitted = dry.value.plan.changes
|
|
68
|
+
.filter((c) => c.type === FileChangeType.Create)
|
|
69
|
+
.map((c) => ({
|
|
70
|
+
absPath: nodePath.resolve(inspection.projectRoot, c.relativePath),
|
|
71
|
+
contents: c.contents ?? '',
|
|
72
|
+
}))
|
|
73
|
+
.filter((f) => f.contents.length > 0 && /\.tsx?$/.test(f.absPath));
|
|
74
|
+
typecheckResult = typecheckEmittedFiles(inspection.projectRoot, emitted);
|
|
75
|
+
// Refuse to write non-compiling output — the whole point of the gate.
|
|
76
|
+
effectiveWrite = write && typecheckResult.errors.length === 0;
|
|
77
|
+
result = effectiveWrite ? generate(template, { ...genOpts, write: true }) : dry;
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
result = generate(template, { ...genOpts, write });
|
|
81
|
+
}
|
|
51
82
|
if (!result.ok) {
|
|
52
83
|
printError(result.error);
|
|
53
84
|
return 1;
|
|
54
85
|
}
|
|
55
86
|
const { plan, summary, written } = result.value;
|
|
87
|
+
const typecheckFailed = (typecheckResult?.errors.length ?? 0) > 0;
|
|
88
|
+
const writeRefused = write && typecheckFailed;
|
|
56
89
|
// --save-plan is allowed regardless of write/dry-run, but we refuse to
|
|
57
90
|
// save plans that have conflicts (since `shrk apply` would refuse them too).
|
|
58
91
|
if (savePlanPath) {
|
|
@@ -89,10 +122,12 @@ export const genCommand = {
|
|
|
89
122
|
summary,
|
|
90
123
|
written: written.map((w) => w.relativePath),
|
|
91
124
|
savedPlanPath: savePlanPath ?? null,
|
|
125
|
+
...(typecheckResult ? { typecheck: typecheckResult } : {}),
|
|
126
|
+
...(writeRefused ? { writeRefused: true } : {}),
|
|
92
127
|
}) + '\n');
|
|
93
|
-
return plan.hasConflicts ? 1 : 0;
|
|
128
|
+
return plan.hasConflicts || typecheckFailed ? 1 : 0;
|
|
94
129
|
}
|
|
95
|
-
process.stdout.write(header(
|
|
130
|
+
process.stdout.write(header(effectiveWrite ? `Generation: ${template.id}` : `Dry-run: ${template.id}`));
|
|
96
131
|
if (plan.warnings.length) {
|
|
97
132
|
process.stdout.write('Warnings:\n');
|
|
98
133
|
for (const w of plan.warnings)
|
|
@@ -118,7 +153,7 @@ export const genCommand = {
|
|
|
118
153
|
// agent can review what a template would generate WITHOUT writing to disk
|
|
119
154
|
// (the saved plan stays content-free). The bytes are identical in dry-run
|
|
120
155
|
// and --write; this is purely additive.
|
|
121
|
-
if (flagBool(args, 'show-content')) {
|
|
156
|
+
if (flagBool(args, 'show-content') || flagBool(args, 'print')) {
|
|
122
157
|
process.stdout.write('\nVirtual content (not written to disk):\n');
|
|
123
158
|
for (const change of plan.changes) {
|
|
124
159
|
const body = change.contents ?? '';
|
|
@@ -139,10 +174,31 @@ export const genCommand = {
|
|
|
139
174
|
if (updateLike.length > 0) {
|
|
140
175
|
process.stdout.write(`\nHUMAN REVIEW REQUIRED — ${updateLike.length} update entry/entries modify existing files.\n`);
|
|
141
176
|
}
|
|
177
|
+
if (typecheckResult) {
|
|
178
|
+
if (!typecheckResult.ran) {
|
|
179
|
+
process.stdout.write(`\nTypecheck: skipped — ${typecheckResult.note ?? 'nothing to check'}.\n`);
|
|
180
|
+
}
|
|
181
|
+
else if (typecheckResult.errors.length === 0) {
|
|
182
|
+
process.stdout.write('\nTypecheck: ✓ emitted files compile against the detected tsconfig.\n');
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
process.stdout.write(`\nTypecheck: ✗ ${typecheckResult.errors.length} error(s) in the emitted files — a template bug (NOT applied):\n`);
|
|
186
|
+
for (const e of typecheckResult.errors.slice(0, 30)) {
|
|
187
|
+
const rel = nodePath.relative(inspection.projectRoot, e.file) || e.file;
|
|
188
|
+
process.stdout.write(` ${rel}:${e.line}:${e.column} ${e.message}\n`);
|
|
189
|
+
}
|
|
190
|
+
if (typecheckResult.errors.length > 30) {
|
|
191
|
+
process.stdout.write(` … (${typecheckResult.errors.length - 30} more)\n`);
|
|
192
|
+
}
|
|
193
|
+
if (writeRefused) {
|
|
194
|
+
process.stdout.write('\nWrite REFUSED — the emitted files do not typecheck (nothing written). Fix the template, then re-run.\n');
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
142
198
|
process.stdout.write(`\nSummary: written=${summary.written}, skipped=${summary.skipped}, conflicts=${summary.conflicts}\n`);
|
|
143
|
-
if (!
|
|
199
|
+
if (!effectiveWrite && !savePlanPath && !writeRefused) {
|
|
144
200
|
process.stdout.write('\nRe-run with --write to apply, or --save-plan <file> + `shrk apply`.\n');
|
|
145
201
|
}
|
|
146
|
-
return plan.hasConflicts ? 1 : 0;
|
|
202
|
+
return plan.hasConflicts || typecheckFailed ? 1 : 0;
|
|
147
203
|
},
|
|
148
204
|
};
|