@shrkcrft/cli 0.1.0-alpha.22 → 0.1.0-alpha.24
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/command-registry.d.ts +9 -0
- package/dist/command-registry.d.ts.map +1 -1
- package/dist/command-registry.js +15 -0
- package/dist/commands/ai-status.command.d.ts.map +1 -1
- package/dist/commands/ai-status.command.js +10 -0
- package/dist/commands/apply.command.d.ts.map +1 -1
- package/dist/commands/apply.command.js +9 -2
- package/dist/commands/ask.command.d.ts.map +1 -1
- package/dist/commands/ask.command.js +2 -1
- package/dist/commands/changes.command.d.ts.map +1 -1
- package/dist/commands/changes.command.js +14 -4
- package/dist/commands/check.command.d.ts.map +1 -1
- package/dist/commands/check.command.js +139 -7
- 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 +291 -0
- package/dist/commands/commands.command.d.ts +15 -0
- package/dist/commands/commands.command.d.ts.map +1 -1
- package/dist/commands/commands.command.js +162 -3
- package/dist/commands/compress.command.d.ts.map +1 -1
- package/dist/commands/compress.command.js +39 -4
- package/dist/commands/context.command.d.ts.map +1 -1
- package/dist/commands/context.command.js +3 -1
- package/dist/commands/daily.commands.d.ts.map +1 -1
- package/dist/commands/daily.commands.js +48 -5
- package/dist/commands/diff-check.command.d.ts.map +1 -1
- package/dist/commands/diff-check.command.js +6 -2
- package/dist/commands/feedback.command.d.ts.map +1 -1
- package/dist/commands/feedback.command.js +4 -0
- 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/framework.command.d.ts.map +1 -1
- package/dist/commands/framework.command.js +20 -3
- package/dist/commands/gate.command.d.ts.map +1 -1
- package/dist/commands/gate.command.js +98 -13
- package/dist/commands/graph-code-subverbs.d.ts.map +1 -1
- package/dist/commands/graph-code-subverbs.js +215 -81
- package/dist/commands/graph.command.d.ts.map +1 -1
- package/dist/commands/graph.command.js +6 -0
- 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 +86 -1
- package/dist/commands/knowledge.command.d.ts.map +1 -1
- package/dist/commands/knowledge.command.js +21 -1
- package/dist/commands/onboard.command.d.ts.map +1 -1
- package/dist/commands/onboard.command.js +11 -2
- package/dist/commands/orchestrate.command.d.ts.map +1 -1
- package/dist/commands/orchestrate.command.js +6 -1
- package/dist/commands/pack-author.command.d.ts.map +1 -1
- package/dist/commands/pack-author.command.js +13 -1
- package/dist/commands/packs.command.d.ts.map +1 -1
- package/dist/commands/packs.command.js +95 -17
- package/dist/commands/paths.command.d.ts.map +1 -1
- package/dist/commands/paths.command.js +8 -1
- package/dist/commands/pipelines.command.d.ts.map +1 -1
- package/dist/commands/pipelines.command.js +2 -1
- package/dist/commands/plan-context.command.d.ts.map +1 -1
- package/dist/commands/plan-context.command.js +6 -3
- package/dist/commands/plan-simulate.command.js +1 -1
- package/dist/commands/policy-lint.command.d.ts.map +1 -1
- package/dist/commands/policy-lint.command.js +20 -7
- package/dist/commands/recommend.command.d.ts.map +1 -1
- package/dist/commands/recommend.command.js +10 -2
- package/dist/commands/registry.command.d.ts.map +1 -1
- package/dist/commands/registry.command.js +117 -9
- package/dist/commands/report.command.js +1 -1
- package/dist/commands/reuse.command.d.ts.map +1 -1
- package/dist/commands/reuse.command.js +17 -5
- package/dist/commands/review.command.d.ts.map +1 -1
- package/dist/commands/review.command.js +20 -2
- package/dist/commands/rule-graph-subverbs.d.ts.map +1 -1
- package/dist/commands/rule-graph-subverbs.js +31 -4
- package/dist/commands/search-structural.command.d.ts.map +1 -1
- package/dist/commands/search-structural.command.js +2 -2
- package/dist/commands/search.command.d.ts.map +1 -1
- package/dist/commands/search.command.js +19 -1
- package/dist/commands/simulate.command.d.ts.map +1 -1
- package/dist/commands/simulate.command.js +6 -1
- package/dist/commands/smart-context.command.d.ts.map +1 -1
- package/dist/commands/smart-context.command.js +101 -41
- package/dist/commands/spec.command.d.ts.map +1 -1
- package/dist/commands/spec.command.js +73 -13
- package/dist/commands/task-context.command.d.ts.map +1 -1
- package/dist/commands/task-context.command.js +26 -2
- package/dist/commands/test.command.d.ts.map +1 -1
- package/dist/commands/test.command.js +17 -0
- package/dist/commands/trace.command.d.ts.map +1 -1
- package/dist/commands/trace.command.js +87 -4
- 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 +19 -1
- package/dist/status/freshness.d.ts +30 -0
- package/dist/status/freshness.d.ts.map +1 -0
- package/dist/status/freshness.js +81 -0
- package/package.json +33 -33
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import { spawn
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
2
|
import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
3
3
|
import * as nodePath from 'node:path';
|
|
4
4
|
import * as os from 'node:os';
|
|
5
5
|
import { AiMessageRole, buildPromptMessages, EnhancementPipeline, EnhancementStageKind, OllamaProvider, buildDefaultEnhancementStages, buildFastEnhancementStages, selectAiProvider, } from '@shrkcrft/ai';
|
|
6
6
|
import { buildContext } from '@shrkcrft/context';
|
|
7
|
+
import { runGitLines } from '@shrkcrft/shared';
|
|
7
8
|
import { EdgeKind, GraphQueryApi, GraphStore, NodeKind } from '@shrkcrft/graph';
|
|
8
|
-
import { buildProjectOverview, buildTaskPacket, inspectSharkcraft, renderOverviewText, } from '@shrkcrft/inspector';
|
|
9
|
+
import { buildProjectOverview, buildTaskPacket, contextTuningBoostFor, inspectSharkcraft, renderOverviewText, } from '@shrkcrft/inspector';
|
|
9
10
|
import { flagBool, flagList, flagNumber, flagString, resolveCwd, } from "../command-registry.js";
|
|
10
11
|
import { DeclarationKind, PLAN_CACHE_SCHEMA, PlanCache, SemanticIndex, TaskType, buildFocusedContext, classifyTask, encodeEmbedding, getDefaultSourceRoots, listIndexableFiles, parseTaskTypeOverride, pruneDeletedHits, renderFocusedContextForPrompt, } from '@shrkcrft/embeddings';
|
|
11
12
|
import { SmartContextDetailedPlanSchema, SmartContextExpansionRequestSchema, } from "../schemas/json-schemas.js";
|
|
@@ -150,6 +151,42 @@ export const smartContextCommand = {
|
|
|
150
151
|
}
|
|
151
152
|
}
|
|
152
153
|
const enh = enhanced.value.enhancement;
|
|
154
|
+
// Deterministic-always contract (CLAUDE.md): when the enhancement
|
|
155
|
+
// pipeline fully degrades — every stage failed (or none ran), so the
|
|
156
|
+
// pipeline's finalOutput is just the echoed system message (preamble +
|
|
157
|
+
// "## Repository context" + the assembled deterministic seed) — fall
|
|
158
|
+
// back to the clean deterministic seed instead of leaking the raw
|
|
159
|
+
// internal prompt to stdout. This mirrors the single-shot
|
|
160
|
+
// provider-failure fallback below: clean renderSeed(seed) content,
|
|
161
|
+
// finishReason 'deterministic-fallback', no LLM/system-prompt wrapper.
|
|
162
|
+
const systemMsg = messages.find((m) => m.role === AiMessageRole.System);
|
|
163
|
+
const originalContext = (systemMsg?.content ?? '').trim();
|
|
164
|
+
const fullyDegraded = (enh.stages.length > 0 && enh.stages.every((s) => s.degraded)) ||
|
|
165
|
+
(originalContext.length > 0 && enhanced.value.content.trim() === originalContext);
|
|
166
|
+
if (fullyDegraded) {
|
|
167
|
+
if (!opts.json) {
|
|
168
|
+
process.stderr.write('[smart-context] enhancement fully degraded — returning deterministic context only.\n');
|
|
169
|
+
}
|
|
170
|
+
const fallbackEnvelope = buildEnvelope({
|
|
171
|
+
task,
|
|
172
|
+
seed,
|
|
173
|
+
ai: {
|
|
174
|
+
content: renderSeed(seed),
|
|
175
|
+
model: 'deterministic',
|
|
176
|
+
finishReason: 'deterministic-fallback',
|
|
177
|
+
usage: null,
|
|
178
|
+
providerId: 'deterministic',
|
|
179
|
+
},
|
|
180
|
+
mode: opts.mode,
|
|
181
|
+
});
|
|
182
|
+
if (opts.save) {
|
|
183
|
+
const saved = saveEnvelope(cwd, fallbackEnvelope);
|
|
184
|
+
writeSavedNotice(saved, opts.json, fallbackEnvelope);
|
|
185
|
+
return 0;
|
|
186
|
+
}
|
|
187
|
+
writeEnvelope(fallbackEnvelope, opts.json, opts.debug);
|
|
188
|
+
return 0;
|
|
189
|
+
}
|
|
153
190
|
if (!opts.json && !enh.deterministicFallback) {
|
|
154
191
|
if (enh.budgetExhausted) {
|
|
155
192
|
process.stderr.write(`[smart-context] budget reached before all ${enh.plannedPasses} passes finished — output is the best so far. Try a smaller --model or raise --budget.\n`);
|
|
@@ -179,6 +216,9 @@ export const smartContextCommand = {
|
|
|
179
216
|
messages,
|
|
180
217
|
maxTokens: opts.maxTokens,
|
|
181
218
|
model: opts.model,
|
|
219
|
+
// Bound the single shot so a hung local model degrades to the
|
|
220
|
+
// deterministic seed below instead of hanging the command.
|
|
221
|
+
timeoutMs: perCallTimeoutMs(opts.budgetMs),
|
|
182
222
|
});
|
|
183
223
|
if (!aiResult.ok) {
|
|
184
224
|
// Deterministic-always contract (CLAUDE.md): a provider failure must
|
|
@@ -293,6 +333,9 @@ export const smartContextPlanAheadCommand = {
|
|
|
293
333
|
messages,
|
|
294
334
|
maxTokens: opts.maxTokens,
|
|
295
335
|
model: opts.model,
|
|
336
|
+
// Bound each task's call so one hung model can't stall the queue;
|
|
337
|
+
// a timeout is reported as that task's error and the loop continues.
|
|
338
|
+
timeoutMs: perCallTimeoutMs(opts.budgetMs),
|
|
296
339
|
});
|
|
297
340
|
if (!aiResult.ok) {
|
|
298
341
|
results.push({ task, status: 'error', error: aiResult.error.message, slug: slug(task) });
|
|
@@ -1418,6 +1461,7 @@ async function buildSmartContextSeed(input) {
|
|
|
1418
1461
|
task,
|
|
1419
1462
|
maxTokens: options.seedTokens,
|
|
1420
1463
|
projectOverview: overviewText,
|
|
1464
|
+
boostFor: contextTuningBoostFor(inspection, task),
|
|
1421
1465
|
});
|
|
1422
1466
|
const graphGrounding = buildInitialGraphGrounding(cwd, task);
|
|
1423
1467
|
const semantic = await tryLoadSemanticHits(cwd, task, 10, options);
|
|
@@ -2139,45 +2183,21 @@ function isSemanticAutomationDisabled() {
|
|
|
2139
2183
|
* ripples through. Empty array on git failure or no-graph.
|
|
2140
2184
|
*/
|
|
2141
2185
|
function collectChangedPathsWithNeighbors(cwd, gitRef) {
|
|
2142
|
-
//
|
|
2143
|
-
//
|
|
2144
|
-
//
|
|
2145
|
-
// gate flags
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
const out = spawnSync('git', ['-C', cwd, 'diff', '--name-only', `${gitRef}...HEAD`], {
|
|
2149
|
-
encoding: 'utf8',
|
|
2150
|
-
});
|
|
2151
|
-
if (out.status !== 0)
|
|
2152
|
-
return [];
|
|
2153
|
-
changed = (out.stdout ?? '')
|
|
2154
|
-
.split('\n')
|
|
2155
|
-
.map((s) => s.trim())
|
|
2156
|
-
.filter((s) => s.length > 0);
|
|
2157
|
-
}
|
|
2158
|
-
catch {
|
|
2159
|
-
return [];
|
|
2160
|
-
}
|
|
2161
|
-
if (changed.length === 0)
|
|
2186
|
+
// `runGitLines` is shell-free + high-maxBuffer, so a large changeset can't
|
|
2187
|
+
// ENOBUFS-crash the feedback bundle. (It also keeps the CLI on
|
|
2188
|
+
// `node:child_process` rather than `Bun.*`, which the compat-node preflight
|
|
2189
|
+
// gate flags as a publish blocker.)
|
|
2190
|
+
const committed = runGitLines(cwd, ['diff', '--name-only', `${gitRef}...HEAD`]);
|
|
2191
|
+
if (!committed.ok || committed.lines.length === 0)
|
|
2162
2192
|
return [];
|
|
2193
|
+
const changed = [...committed.lines];
|
|
2163
2194
|
// Also include uncommitted changes — agent feedback should reflect
|
|
2164
2195
|
// the *current* working tree, not just committed deltas.
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
const uncommitted = (out.stdout ?? '')
|
|
2171
|
-
.split('\n')
|
|
2172
|
-
.map((s) => s.trim())
|
|
2173
|
-
.filter((s) => s.length > 0);
|
|
2174
|
-
for (const p of uncommitted)
|
|
2175
|
-
if (!changed.includes(p))
|
|
2176
|
-
changed.push(p);
|
|
2177
|
-
}
|
|
2178
|
-
}
|
|
2179
|
-
catch {
|
|
2180
|
-
// ignore
|
|
2196
|
+
const uncommitted = runGitLines(cwd, ['diff', '--name-only', 'HEAD']);
|
|
2197
|
+
if (uncommitted.ok) {
|
|
2198
|
+
for (const p of uncommitted.lines)
|
|
2199
|
+
if (!changed.includes(p))
|
|
2200
|
+
changed.push(p);
|
|
2181
2201
|
}
|
|
2182
2202
|
const set = new Set(changed);
|
|
2183
2203
|
// One-hop graph expansion if the graph is fresh.
|
|
@@ -2693,6 +2713,23 @@ function truncateLine(text, max) {
|
|
|
2693
2713
|
const PER_STAGE_TIMEOUT_MS = 90_000;
|
|
2694
2714
|
const FAST_ENHANCE_BUDGET_MS = 150_000;
|
|
2695
2715
|
const PLUS_ENHANCE_BUDGET_MS = 360_000;
|
|
2716
|
+
/**
|
|
2717
|
+
* Per-call wall-clock bounds for the paths that DON'T run through
|
|
2718
|
+
* `runEnhancementPipeline` (single-shot brief, plan-ahead, and the two
|
|
2719
|
+
* `--ai-plan` stages). Without these, `--budget` only constrained the
|
|
2720
|
+
* enhancement pipeline, so a hung local model could still hang those modes.
|
|
2721
|
+
*
|
|
2722
|
+
* - `SINGLE_CALL_DEFAULT_BUDGET_MS` — the cap applied when no `--budget` is
|
|
2723
|
+
* given (mirrors the fast-enhance ceiling).
|
|
2724
|
+
* - `PER_CALL_TIMEOUT_CAP_MS` — an absolute ceiling, so even a generous
|
|
2725
|
+
* `--budget` can't let a single call run unbounded.
|
|
2726
|
+
*/
|
|
2727
|
+
const SINGLE_CALL_DEFAULT_BUDGET_MS = 150_000;
|
|
2728
|
+
const PER_CALL_TIMEOUT_CAP_MS = 360_000;
|
|
2729
|
+
/** Derive a per-call timeout (ms) from the optional `--budget` (budgetMs). */
|
|
2730
|
+
function perCallTimeoutMs(budgetMs) {
|
|
2731
|
+
return Math.min(budgetMs ?? SINGLE_CALL_DEFAULT_BUDGET_MS, PER_CALL_TIMEOUT_CAP_MS);
|
|
2732
|
+
}
|
|
2696
2733
|
async function runEnhancementPipeline(input) {
|
|
2697
2734
|
const provider = input.provider;
|
|
2698
2735
|
const systemMsg = input.messages.find((m) => m.role === AiMessageRole.System);
|
|
@@ -2830,6 +2867,7 @@ async function callProvider(input) {
|
|
|
2830
2867
|
...(input.model ? { model: input.model } : {}),
|
|
2831
2868
|
...(input.responseFormat ? { responseFormat: input.responseFormat } : {}),
|
|
2832
2869
|
...(input.onTokenStream ? { onTokenStream: input.onTokenStream } : {}),
|
|
2870
|
+
...(input.timeoutMs !== undefined ? { timeoutMs: input.timeoutMs } : {}),
|
|
2833
2871
|
});
|
|
2834
2872
|
if (!res.ok || !res.value)
|
|
2835
2873
|
return { ok: false, error: res.error };
|
|
@@ -2927,10 +2965,23 @@ function writeDryRun(messages, mode, provider) {
|
|
|
2927
2965
|
}
|
|
2928
2966
|
}
|
|
2929
2967
|
function displayProviderName(explicit) {
|
|
2930
|
-
|
|
2931
|
-
|
|
2968
|
+
// Mirror provider-resolver.ts `normaliseKind` so the --dry-run header names
|
|
2969
|
+
// the provider that will ACTUALLY run, not a stale 'auto'. An explicit
|
|
2970
|
+
// `auto` is a local-first request that AI_PROVIDER never overrides; with no
|
|
2971
|
+
// --provider, AI_PROVIDER (including a hosted claude/gemini) decides what
|
|
2972
|
+
// runs, so the header must say so truthfully.
|
|
2973
|
+
if (explicit !== undefined) {
|
|
2974
|
+
const e = explicit.trim().toLowerCase();
|
|
2975
|
+
if (e === 'auto')
|
|
2976
|
+
return 'auto';
|
|
2977
|
+
if (e === 'claude' || e === 'gemini' || e === 'ollama' || e === 'llamacpp')
|
|
2978
|
+
return e;
|
|
2979
|
+
}
|
|
2932
2980
|
const envProvider = (process.env.AI_PROVIDER ?? '').trim().toLowerCase();
|
|
2933
|
-
if (envProvider === 'ollama' ||
|
|
2981
|
+
if (envProvider === 'ollama' ||
|
|
2982
|
+
envProvider === 'llamacpp' ||
|
|
2983
|
+
envProvider === 'claude' ||
|
|
2984
|
+
envProvider === 'gemini') {
|
|
2934
2985
|
return envProvider;
|
|
2935
2986
|
}
|
|
2936
2987
|
return 'auto';
|
|
@@ -3212,6 +3263,11 @@ async function buildAiPlanEnvelope(input) {
|
|
|
3212
3263
|
}
|
|
3213
3264
|
progressMarker(`preflight ok — host=${preflight.value.host} models=${preflight.value.models.length}`, input.options);
|
|
3214
3265
|
}
|
|
3266
|
+
// Split the (optional) --budget across the two stages so a hung local
|
|
3267
|
+
// model can't hang the ai-plan path the way it previously could. Each
|
|
3268
|
+
// call is independently bounded; a TIMEOUT surfaces as a call-failure and
|
|
3269
|
+
// is handled exactly like any other provider failure below.
|
|
3270
|
+
const aiPlanStageTimeoutMs = Math.min(Math.floor((input.options.budgetMs ?? SINGLE_CALL_DEFAULT_BUDGET_MS) / 2), PER_CALL_TIMEOUT_CAP_MS);
|
|
3215
3271
|
progressMarker(`stage 1 calling ${selection.provider.id}${input.options.model ? `:${input.options.model}` : ''}…`, input.options);
|
|
3216
3272
|
const stage1Messages = buildStage1Messages(input.seed, grounding, cacheLookup.reference);
|
|
3217
3273
|
logPromptToStderr('stage1', stage1Messages, input.options);
|
|
@@ -3229,6 +3285,7 @@ async function buildAiPlanEnvelope(input) {
|
|
|
3229
3285
|
repromptInstruction: STAGE1_REPROMPT,
|
|
3230
3286
|
stageLabel: 'stage 1',
|
|
3231
3287
|
options: input.options,
|
|
3288
|
+
timeoutMs: aiPlanStageTimeoutMs,
|
|
3232
3289
|
});
|
|
3233
3290
|
let stage1Request;
|
|
3234
3291
|
let stage1Retried = false;
|
|
@@ -3275,6 +3332,7 @@ async function buildAiPlanEnvelope(input) {
|
|
|
3275
3332
|
repromptInstruction: STAGE2_REPROMPT,
|
|
3276
3333
|
stageLabel: 'stage 2',
|
|
3277
3334
|
options: input.options,
|
|
3335
|
+
timeoutMs: aiPlanStageTimeoutMs,
|
|
3278
3336
|
});
|
|
3279
3337
|
const conversationTurns = [];
|
|
3280
3338
|
if (stage1Call) {
|
|
@@ -3424,6 +3482,7 @@ async function callProviderWithRetry(input) {
|
|
|
3424
3482
|
maxTokens: input.maxTokens,
|
|
3425
3483
|
...(input.model ? { model: input.model } : {}),
|
|
3426
3484
|
...(input.responseFormat ? { responseFormat: input.responseFormat } : {}),
|
|
3485
|
+
...(input.timeoutMs !== undefined ? { timeoutMs: input.timeoutMs } : {}),
|
|
3427
3486
|
});
|
|
3428
3487
|
if (!first.ok)
|
|
3429
3488
|
return { kind: 'call-failed', error: first.error };
|
|
@@ -3449,6 +3508,7 @@ async function callProviderWithRetry(input) {
|
|
|
3449
3508
|
maxTokens: input.maxTokens,
|
|
3450
3509
|
...(input.model ? { model: input.model } : {}),
|
|
3451
3510
|
...(input.responseFormat ? { responseFormat: input.responseFormat } : {}),
|
|
3511
|
+
...(input.timeoutMs !== undefined ? { timeoutMs: input.timeoutMs } : {}),
|
|
3452
3512
|
});
|
|
3453
3513
|
if (!second.ok) {
|
|
3454
3514
|
return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spec.command.d.ts","sourceRoot":"","sources":["../../src/commands/spec.command.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AA0CH,OAAO,
|
|
1
|
+
{"version":3,"file":"spec.command.d.ts","sourceRoot":"","sources":["../../src/commands/spec.command.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AA0CH,OAAO,EAUL,gBAAgB,EAMjB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAKL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAShC,eAAO,MAAM,qBAAqB,iCAAiC,CAAC;AACpE,eAAO,MAAM,wBAAwB,oCAAoC,CAAC;AAY1E,eAAO,MAAM,iBAAiB,EAAE,eA2K/B,CAAC;AAcF,eAAO,MAAM,iBAAiB,EAAE,eAiE/B,CAAC;AAqBF,eAAO,MAAM,eAAe,EAAE,eAmC7B,CAAC;AAEF,eAAO,MAAM,eAAe,EAAE,eA8B7B,CAAC;AAEF,eAAO,MAAM,eAAe,EAAE,eA2C7B,CAAC;AAEF,eAAO,MAAM,iBAAiB,EAAE,eAoE/B,CAAC;AASF,eAAO,MAAM,oBAAoB,EAAE,eAsRlC,CAAC;AAyDF,eAAO,MAAM,iBAAiB,EAAE,eAqP/B,CAAC;AAEF,eAAO,MAAM,iBAAiB,EAAE,eA4B/B,CAAC;AAkBF,OAAO,EAAE,gBAAgB,EAAE,CAAC"}
|
|
@@ -17,7 +17,7 @@ import { createHash } from 'node:crypto';
|
|
|
17
17
|
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
18
18
|
import * as nodePath from 'node:path';
|
|
19
19
|
import { appendSpecEvent, buildSavedPlan, buildSpecId, canonicalJson, deriveSpecJson, FileChangeType, generate, listSpecIds, loadSpec, OverwriteStrategy, persistSpecArtifacts, PLAN_SECRET_ENV, readSpecEvents, renderSpecMd, savePlanToFile, signPlan, specDir, specMdPath, specPlanPath, specVerificationPath, splitSpecMd, SPEC_SCHEMA_V1, SpecStatus, validateSpecStructural, verifyPlan, writeSpecJson, writeSpecMd, } from '@shrkcrft/generator';
|
|
20
|
-
import { AssetProvenanceOperation, AssetProvenanceSource, buildSpecList, buildSpecReview, buildTaskPacket, inspectSharkcraft, recordProvenance, SPEC_LIST_SCHEMA, } from '@shrkcrft/inspector';
|
|
20
|
+
import { AssetProvenanceOperation, AssetProvenanceSource, buildSpecList, buildSpecReview, buildTaskPacket, inspectSharkcraft, mapChecklistToEvidence, recordProvenance, SPEC_LIST_SCHEMA, } from '@shrkcrft/inspector';
|
|
21
21
|
import { flagBool, flagList, flagString, resolveCwd, } from "../command-registry.js";
|
|
22
22
|
import { collectChangedPaths } from "../diff/collect-changed-paths.js";
|
|
23
23
|
import { asJson, header } from "../output/format-output.js";
|
|
@@ -778,8 +778,8 @@ function shortHash(text) {
|
|
|
778
778
|
}
|
|
779
779
|
export const specVerifyCommand = {
|
|
780
780
|
name: 'verify',
|
|
781
|
-
description: 'Run the spec\'s trusted verification commands, check acceptance-criteria coverage, run diff-aware boundary + drift checks. Returns sharkcraft.spec-verification/v1.',
|
|
782
|
-
usage: 'shrk spec verify <id> [--since <ref>] [--strict] [--skip-verification-commands] [--json] [--write]',
|
|
781
|
+
description: 'Run the spec\'s trusted verification commands, check acceptance-criteria coverage, run diff-aware boundary + drift checks. Pass --coverage to map each acceptance criterion to backing changeset evidence and flag UNMET (claimed-but-zero-footprint) criteria. Returns sharkcraft.spec-verification/v1.',
|
|
782
|
+
usage: 'shrk spec verify <id> [--since <ref>] [--coverage] [--strict] [--skip-verification-commands] [--json] [--write]',
|
|
783
783
|
async run(args) {
|
|
784
784
|
const ref = args.positional[0];
|
|
785
785
|
if (!ref) {
|
|
@@ -886,18 +886,51 @@ export const specVerifyCommand = {
|
|
|
886
886
|
planIntegrity = 'invalid-signature';
|
|
887
887
|
}
|
|
888
888
|
}
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
889
|
+
// --coverage: map each acceptance criterion to backing changeset
|
|
890
|
+
// evidence (new exported symbol / registration / route / companion
|
|
891
|
+
// file / test). UNMET = a claimed criterion with zero footprint.
|
|
892
|
+
const wantCoverage = flagBool(args, 'coverage');
|
|
893
|
+
let coverage = null;
|
|
894
|
+
if (wantCoverage) {
|
|
895
|
+
const fileContents = {};
|
|
896
|
+
if (changed.isAvailable) {
|
|
897
|
+
for (const rel of changed.changed) {
|
|
898
|
+
const abs = nodePath.join(cwd, rel);
|
|
899
|
+
if (existsSync(abs)) {
|
|
900
|
+
try {
|
|
901
|
+
fileContents[rel] = readFileSync(abs, 'utf8');
|
|
902
|
+
}
|
|
903
|
+
catch {
|
|
904
|
+
fileContents[rel] = '';
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
coverage = mapChecklistToEvidence({
|
|
910
|
+
criteria: spec.acceptanceCriteria.map((ac) => ({ id: ac.id, text: ac.text })),
|
|
911
|
+
changedFiles: changed.changed,
|
|
912
|
+
fileContents,
|
|
913
|
+
});
|
|
914
|
+
}
|
|
915
|
+
const coverageById = new Map((coverage?.criteria ?? []).map((c) => [c.id, c]));
|
|
916
|
+
const acceptanceResults = spec.acceptanceCriteria.map((ac) => {
|
|
917
|
+
const cov = coverageById.get(ac.id);
|
|
918
|
+
return {
|
|
919
|
+
id: ac.id,
|
|
920
|
+
status: ac.verifiedBy.includes('manual')
|
|
921
|
+
? 'manual'
|
|
922
|
+
: commandFailed
|
|
923
|
+
? 'fail'
|
|
924
|
+
: 'pass',
|
|
925
|
+
evidence: cov?.evidence ?? [],
|
|
926
|
+
...(wantCoverage ? { covered: cov?.covered ?? false } : {}),
|
|
927
|
+
};
|
|
928
|
+
});
|
|
898
929
|
const verdict = commandFailed
|
|
899
930
|
? 'fail'
|
|
900
|
-
: scopeDrift.length > 0 ||
|
|
931
|
+
: scopeDrift.length > 0 ||
|
|
932
|
+
planIntegrity === 'missing-signature' ||
|
|
933
|
+
(coverage !== null && coverage.unmetCount > 0)
|
|
901
934
|
? 'warn'
|
|
902
935
|
: 'pass';
|
|
903
936
|
const report = {
|
|
@@ -911,6 +944,17 @@ export const specVerifyCommand = {
|
|
|
911
944
|
boundaries: { since: changed.ref, violations: [] },
|
|
912
945
|
scopeDrift: { outsideScope: scopeDrift },
|
|
913
946
|
planIntegrity: { status: planIntegrity },
|
|
947
|
+
...(coverage !== null
|
|
948
|
+
? {
|
|
949
|
+
coverage: {
|
|
950
|
+
checked: true,
|
|
951
|
+
total: coverage.criteria.length,
|
|
952
|
+
covered: coverage.coveredCount,
|
|
953
|
+
unmet: coverage.unmetCount,
|
|
954
|
+
changesetAvailable: changed.isAvailable,
|
|
955
|
+
},
|
|
956
|
+
}
|
|
957
|
+
: {}),
|
|
914
958
|
};
|
|
915
959
|
if (flagBool(args, 'write') || flagBool(args, 'apply')) {
|
|
916
960
|
writeFileSync(specVerificationPath(cwd, spec.id), JSON.stringify(report, null, 2) + '\n', 'utf8');
|
|
@@ -935,6 +979,22 @@ export const specVerifyCommand = {
|
|
|
935
979
|
}
|
|
936
980
|
process.stdout.write(` scope drift: ${scopeDrift.length}\n`);
|
|
937
981
|
process.stdout.write(` plan integrity: ${planIntegrity}\n`);
|
|
982
|
+
if (coverage !== null) {
|
|
983
|
+
process.stdout.write(` coverage: ${coverage.coveredCount}/${coverage.criteria.length} covered, ${coverage.unmetCount} UNMET\n`);
|
|
984
|
+
if (!changed.isAvailable) {
|
|
985
|
+
process.stdout.write(' (no changeset available — pass --since <ref> for evidence)\n');
|
|
986
|
+
}
|
|
987
|
+
for (const c of coverage.criteria) {
|
|
988
|
+
if (c.covered) {
|
|
989
|
+
const ev = c.evidence[0];
|
|
990
|
+
const note = ev ? ` (${ev.kind}: ${ev.detail})` : '';
|
|
991
|
+
process.stdout.write(` [ok] ${c.id}${note}\n`);
|
|
992
|
+
}
|
|
993
|
+
else {
|
|
994
|
+
process.stdout.write(` UNMET ${c.id} — ${c.text.slice(0, 60)}\n`);
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
}
|
|
938
998
|
}
|
|
939
999
|
if (verdict === 'fail')
|
|
940
1000
|
return 1;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"task-context.command.d.ts","sourceRoot":"","sources":["../../src/commands/task-context.command.ts"],"names":[],"mappings":"AAuBA,OAAO,EAKL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"task-context.command.d.ts","sourceRoot":"","sources":["../../src/commands/task-context.command.ts"],"names":[],"mappings":"AAuBA,OAAO,EAKL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAchC,eAAO,MAAM,qBAAqB,EAAE,eAyEnC,CAAC;AAEF,eAAO,MAAM,qBAAqB,EAAE,eAsEnC,CAAC;AAIF,eAAO,MAAM,mBAAmB,EAAE,eA2DjC,CAAC;AAEF,eAAO,MAAM,qBAAqB,EAAE,eAqBnC,CAAC;AAEF,eAAO,MAAM,oBAAoB,EAAE,eA8ClC,CAAC"}
|
|
@@ -4,6 +4,7 @@ import { cliCommandNameSet } from "./command-catalog.js";
|
|
|
4
4
|
import { analyzeImportGraph, buildAgentBrief, buildContradictionReport, buildGeneratedCodeReport, buildRepositoryKnowledgeModel, buildTaskPacket, buildTaskRiskReport, classifyChangeIntent, detectLanguageProfiles, getChangedFiles, getStatusSummary, inspectSharkcraft, isGitRepo, listConstructs, loadRepositoryMemory, } from '@shrkcrft/inspector';
|
|
5
5
|
import { flagBool, flagString, flagList, resolveCwd, } from "../command-registry.js";
|
|
6
6
|
import { asJson, header, kv } from "../output/format-output.js";
|
|
7
|
+
import { computeMtimeFreshness } from "../status/freshness.js";
|
|
7
8
|
const CONTEXT_BASE = nodePath.join('.sharkcraft', 'context');
|
|
8
9
|
function slug(s) {
|
|
9
10
|
return s
|
|
@@ -244,11 +245,34 @@ export const contextStatusCommand = {
|
|
|
244
245
|
}
|
|
245
246
|
const { readFileSync } = await import('node:fs');
|
|
246
247
|
const body = readFileSync(file, 'utf8');
|
|
248
|
+
let status;
|
|
249
|
+
try {
|
|
250
|
+
status = JSON.parse(body);
|
|
251
|
+
}
|
|
252
|
+
catch {
|
|
253
|
+
status = {};
|
|
254
|
+
}
|
|
255
|
+
// Honest freshness: the saved context is stale once a source file changed
|
|
256
|
+
// after it was built. Adds `state` + a warn line in both shapes.
|
|
257
|
+
const fresh = computeMtimeFreshness(cwd, status.lastBuilt);
|
|
247
258
|
if (flagBool(args, 'json')) {
|
|
248
|
-
process.stdout.write(
|
|
259
|
+
process.stdout.write(asJson({
|
|
260
|
+
...status,
|
|
261
|
+
state: fresh.state,
|
|
262
|
+
lastBuiltAt: fresh.lastBuiltAt,
|
|
263
|
+
lastChangedAt: fresh.lastChangedAt,
|
|
264
|
+
behindMs: fresh.behindMs,
|
|
265
|
+
...(fresh.state === 'stale' ? { nextCommand: 'shrk context refresh' } : {}),
|
|
266
|
+
}) + '\n');
|
|
249
267
|
return 0;
|
|
250
268
|
}
|
|
251
|
-
process.stdout.write(
|
|
269
|
+
process.stdout.write(header('Context status'));
|
|
270
|
+
process.stdout.write(kv('last task', status.lastTask ?? '-') + '\n');
|
|
271
|
+
process.stdout.write(kv('last built', status.lastBuilt ?? '-') + '\n');
|
|
272
|
+
process.stdout.write(kv('state', fresh.state) + '\n');
|
|
273
|
+
if (fresh.state === 'stale') {
|
|
274
|
+
process.stdout.write(`! stale — files changed since this context was built${fresh.lastChangedAt ? ` (last change ${fresh.lastChangedAt})` : ''}; re-run \`shrk context refresh\`\n`);
|
|
275
|
+
}
|
|
252
276
|
return 0;
|
|
253
277
|
},
|
|
254
278
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"test.command.d.ts","sourceRoot":"","sources":["../../src/commands/test.command.ts"],"names":[],"mappings":"AAUA,OAAO,EAKL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"test.command.d.ts","sourceRoot":"","sources":["../../src/commands/test.command.ts"],"names":[],"mappings":"AAUA,OAAO,EAKL,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AAiShC,eAAO,MAAM,WAAW,EAAE,eA4BzB,CAAC"}
|
|
@@ -3,6 +3,7 @@ import * as nodePath from 'node:path';
|
|
|
3
3
|
import { buildTaskPacket, inspectSharkcraft, loadAgentContractTests, loadContextTests, runAgentContractTest, runContextTest, } from '@shrkcrft/inspector';
|
|
4
4
|
import { flagBool, flagList, flagString, resolveCwd, } from "../command-registry.js";
|
|
5
5
|
import { asJson, header, kv } from "../output/format-output.js";
|
|
6
|
+
import { agentTestMissingExpectedHints, renderFailureHints } from "../output/failure-hints.js";
|
|
6
7
|
import { maybeRunInWatchMode } from "../output/watch-loop.js";
|
|
7
8
|
async function runContextTests(args) {
|
|
8
9
|
const inspection = await inspectSharkcraft({ cwd: resolveCwd(args) });
|
|
@@ -114,6 +115,22 @@ async function runAgentTests(args) {
|
|
|
114
115
|
}
|
|
115
116
|
}
|
|
116
117
|
process.stdout.write(`\nSummary: ${results.length - failed.length}/${results.length} passed.\n`);
|
|
118
|
+
// Surface explain/why hints when a failure lists an expected construct the
|
|
119
|
+
// packet didn't include — that's the case the `shrk why`/`why-not` family
|
|
120
|
+
// is built to diagnose.
|
|
121
|
+
const anyMissingExpected = failed.some((r) => (r.missingTemplates ?? []).length > 0 ||
|
|
122
|
+
(r.missingRules ?? []).length > 0 ||
|
|
123
|
+
(r.missingForbiddenActions ?? []).length > 0 ||
|
|
124
|
+
(r.missingVerificationCommands ?? []).length > 0 ||
|
|
125
|
+
(r.missingHelpers ?? []).length > 0 ||
|
|
126
|
+
(r.missingPlaybooks ?? []).length > 0 ||
|
|
127
|
+
(r.missingPolicies ?? []).length > 0 ||
|
|
128
|
+
(r.missingConstructs ?? []).length > 0 ||
|
|
129
|
+
(r.missingCommands ?? []).length > 0 ||
|
|
130
|
+
(r.missingKnowledge ?? []).length > 0);
|
|
131
|
+
if (anyMissingExpected) {
|
|
132
|
+
process.stdout.write(renderFailureHints(agentTestMissingExpectedHints()));
|
|
133
|
+
}
|
|
117
134
|
return failed.length > 0 ? 1 : 0;
|
|
118
135
|
}
|
|
119
136
|
async function generateContextTest(args) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"trace.command.d.ts","sourceRoot":"","sources":["../../src/commands/trace.command.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"trace.command.d.ts","sourceRoot":"","sources":["../../src/commands/trace.command.ts"],"names":[],"mappings":"AAmBA,OAAO,EAML,KAAK,eAAe,EAErB,MAAM,wBAAwB,CAAC;AA6EhC,eAAO,MAAM,YAAY,EAAE,eAwH1B,CAAC"}
|
|
@@ -6,16 +6,86 @@
|
|
|
6
6
|
* shared query resolver and prints structured trace output.
|
|
7
7
|
*/
|
|
8
8
|
import { findSymbolInProject, inspectSharkcraft, QueryMatchKind, resolveQuery, } from '@shrkcrft/inspector';
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
9
|
+
import { traceLiteral, TraceRole, } from '@shrkcrft/boundaries';
|
|
10
|
+
import { flagBool, flagList, flagNumber, flagString, resolveCwd, } from "../command-registry.js";
|
|
11
|
+
import { asJson, header, kv } from "../output/format-output.js";
|
|
11
12
|
function describeMatch(m) {
|
|
12
13
|
return `${m.kind.padEnd(12)} ${m.id}${m.label && m.label !== m.id ? ` — ${m.label}` : ''} [${m.score.toFixed(0)}]`;
|
|
13
14
|
}
|
|
15
|
+
const LITERAL_SITE_CAP = 30;
|
|
16
|
+
const TRACE_ROLE_ORDER = [
|
|
17
|
+
TraceRole.Declare,
|
|
18
|
+
TraceRole.Register,
|
|
19
|
+
TraceRole.Consume,
|
|
20
|
+
TraceRole.Reference,
|
|
21
|
+
];
|
|
22
|
+
function renderTraceLiteral(report, limit) {
|
|
23
|
+
process.stdout.write(header(`Trace literal: "${report.literal}"`));
|
|
24
|
+
process.stdout.write(kv('found', `${report.total} site(s) across ${report.files} file(s)`) + '\n');
|
|
25
|
+
if (report.aliases.length > 0) {
|
|
26
|
+
process.stdout.write(kv('const aliases', report.aliases.join(', ')) + '\n');
|
|
27
|
+
}
|
|
28
|
+
if (report.total === 0) {
|
|
29
|
+
process.stdout.write('\nNo occurrences of that exact string literal in scope.\n');
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
// Mirror `shrk registry <name> where`'s `<role> file:line` line idiom so the
|
|
33
|
+
// two surfaces read the same — `trace literal` is the same scanner + classifier
|
|
34
|
+
// without a pre-declared registry, just with two extra roles (`registered`,
|
|
35
|
+
// `reference`). The raw source line stays in `--json` (`text`); the human view
|
|
36
|
+
// is classification-first (direction), not a grep-style text dump.
|
|
37
|
+
process.stdout.write('\n');
|
|
38
|
+
for (const role of TRACE_ROLE_ORDER) {
|
|
39
|
+
const sites = report.byRole[role];
|
|
40
|
+
for (const s of sites.slice(0, limit)) {
|
|
41
|
+
const alias = s.viaAlias ? ` [via ${s.viaAlias}]` : '';
|
|
42
|
+
process.stdout.write(` ${role.padEnd(10)} ${s.file}:${s.line}${alias}\n`);
|
|
43
|
+
}
|
|
44
|
+
if (sites.length > limit) {
|
|
45
|
+
process.stdout.write(` ${role.padEnd(10)} … (${sites.length - limit} more — pass --json)\n`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* `shrk trace literal "<string>"`: trace an EXACT string literal across the
|
|
51
|
+
* codebase, classified by direction (declare → register → consume), resolving
|
|
52
|
+
* `const X = "lit"` aliases too. Generalizes `registry … where` to any
|
|
53
|
+
* cross-fence string contract (a kind slug, permission id, route key) with no
|
|
54
|
+
* pre-declared registry — the chain grep can't give (direction + role + layer).
|
|
55
|
+
*/
|
|
56
|
+
function runTraceLiteral(args) {
|
|
57
|
+
const cwd = resolveCwd(args);
|
|
58
|
+
const wantJson = flagBool(args, 'json');
|
|
59
|
+
// `trace literal "<lit>"` → the literal is positional[1] (quoted by the user
|
|
60
|
+
// so an internal space stays one token); join the rest defensively.
|
|
61
|
+
const literal = args.positional.slice(1).join(' ');
|
|
62
|
+
if (literal === '') {
|
|
63
|
+
process.stderr.write('Usage: shrk trace literal "<string>" [--glob <g1,g2>] [--no-aliases] [--limit N] [--json]\n');
|
|
64
|
+
return 2;
|
|
65
|
+
}
|
|
66
|
+
const globs = flagList(args, 'glob');
|
|
67
|
+
const report = traceLiteral(cwd, literal, {
|
|
68
|
+
...(globs.length > 0 ? { globs } : {}),
|
|
69
|
+
resolveAliases: !flagBool(args, 'no-aliases'),
|
|
70
|
+
});
|
|
71
|
+
if (wantJson) {
|
|
72
|
+
process.stdout.write(asJson(report) + '\n');
|
|
73
|
+
return 0;
|
|
74
|
+
}
|
|
75
|
+
const limitRaw = flagNumber(args, 'limit');
|
|
76
|
+
const limit = limitRaw !== undefined && limitRaw > 0 ? Math.floor(limitRaw) : LITERAL_SITE_CAP;
|
|
77
|
+
renderTraceLiteral(report, limit);
|
|
78
|
+
return 0;
|
|
79
|
+
}
|
|
14
80
|
export const traceCommand = {
|
|
15
81
|
name: 'trace',
|
|
16
|
-
description: 'Fuzzy trace — accept any free-form query (file path, construct id, symbol, plugin key, helper id, template id, knowledge id, command). Read-only.',
|
|
17
|
-
usage: 'shrk trace <query> [--limit <n>] [--kind
|
|
82
|
+
description: 'Fuzzy trace — accept any free-form query (file path, construct id, symbol, plugin key, helper id, template id, knowledge id, command). `trace literal "<string>"` traces an exact string literal\'s declare→register→consume chain across layers (alias-resolved). Read-only.',
|
|
83
|
+
usage: 'shrk trace <query> | shrk trace literal "<string>" [--glob <g>] [--no-aliases] [--limit <n>] [--kind ...] [--deep] [--json]',
|
|
84
|
+
booleanFlags: new Set(['json', 'deep', 'no-aliases']),
|
|
18
85
|
async run(args) {
|
|
86
|
+
// `trace literal "<lit>"` — cross-fence string-contract tracer (3.3).
|
|
87
|
+
if (args.positional[0] === 'literal')
|
|
88
|
+
return runTraceLiteral(args);
|
|
19
89
|
// Direct symbol trace via --symbol <Name>
|
|
20
90
|
const symbol = flagString(args, 'symbol');
|
|
21
91
|
if (symbol) {
|
|
@@ -58,10 +128,23 @@ export const traceCommand = {
|
|
|
58
128
|
process.stderr.write('Usage: shrk trace <query> | shrk trace --symbol <Name>\n');
|
|
59
129
|
return 2;
|
|
60
130
|
}
|
|
131
|
+
// `--kind` restricts the result set to specific match kinds — the engine
|
|
132
|
+
// (resolveQuery) filters by them. Validate up front so an unknown kind
|
|
133
|
+
// fails fast with the valid list, instead of being silently ignored (which
|
|
134
|
+
// returned a wrong-kind best match, e.g. a knowledge hit for
|
|
135
|
+
// `--kind template`).
|
|
136
|
+
const kinds = flagList(args, 'kind');
|
|
137
|
+
const validKinds = new Set(Object.values(QueryMatchKind));
|
|
138
|
+
const unknownKind = kinds.find((k) => !validKinds.has(k));
|
|
139
|
+
if (unknownKind) {
|
|
140
|
+
process.stderr.write(`trace: unknown --kind "${unknownKind}". Valid kinds: ${[...validKinds].sort().join(', ')}.\n`);
|
|
141
|
+
return 2;
|
|
142
|
+
}
|
|
61
143
|
const cwd = resolveCwd(args);
|
|
62
144
|
const inspection = await inspectSharkcraft({ cwd });
|
|
63
145
|
const limit = flagNumber(args, 'limit');
|
|
64
146
|
const resolution = resolveQuery(inspection, query, {
|
|
147
|
+
...(kinds.length > 0 ? { kinds: kinds } : {}),
|
|
65
148
|
...(typeof limit === 'number' ? { limit } : {}),
|
|
66
149
|
});
|
|
67
150
|
if (flagBool(args, 'json')) {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type IWiringExplain } from '@shrkcrft/boundaries';
|
|
2
|
+
import { type ICommandHandler } from '../command-registry.js';
|
|
3
|
+
/**
|
|
4
|
+
* Render an {@link IWiringExplain} to stdout. Shared by `wiring explain`,
|
|
5
|
+
* `wiring test`, and `check wiring --explain` so all three speak the same
|
|
6
|
+
* dialect. JSON emits the full payload; text mirrors `search tuning explain`
|
|
7
|
+
* (header → loaded sets → per-site detail → the set-difference → verdict).
|
|
8
|
+
* Always returns 0 — explain is informational, the verdict is in the output.
|
|
9
|
+
*/
|
|
10
|
+
export declare function renderWiringExplain(report: IWiringExplain, wantJson: boolean): number;
|
|
11
|
+
export declare const wiringCommand: ICommandHandler;
|
|
12
|
+
//# sourceMappingURL=wiring.command.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wiring.command.d.ts","sourceRoot":"","sources":["../../src/commands/wiring.command.ts"],"names":[],"mappings":"AAAA,OAAO,EASL,KAAK,cAAc,EACpB,MAAM,sBAAsB,CAAC;AAM9B,OAAO,EAAwB,KAAK,eAAe,EAAmB,MAAM,wBAAwB,CAAC;AAKrG;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,cAAc,EAAE,QAAQ,EAAE,OAAO,GAAG,MAAM,CAsDrF;AA+TD,eAAO,MAAM,aAAa,EAAE,eAgB3B,CAAC"}
|