@gajae-code/coding-agent 0.11.2 → 0.11.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +34 -0
- package/dist/types/cli/launch-disposition.d.ts +23 -0
- package/dist/types/cli/worktree-cli.d.ts +8 -25
- package/dist/types/commands/gc.d.ts +4 -0
- package/dist/types/commands/worktree.d.ts +7 -32
- package/dist/types/config/settings-schema.d.ts +85 -10
- package/dist/types/gjc-runtime/gc-runtime.d.ts +9 -0
- package/dist/types/gjc-runtime/state-argv.d.ts +38 -0
- package/dist/types/main.d.ts +1 -0
- package/dist/types/modes/components/custom-editor.d.ts +8 -3
- package/dist/types/modes/components/tool-transcript-format.d.ts +19 -0
- package/dist/types/modes/controllers/input-controller.d.ts +8 -2
- package/dist/types/modes/prompt-action-autocomplete.d.ts +4 -0
- package/dist/types/sdk/broker/broker.d.ts +1 -0
- package/dist/types/sdk/broker/discovery.d.ts +10 -0
- package/dist/types/sdk/broker/ensure.d.ts +24 -1
- package/dist/types/sdk/broker/internal.d.ts +3 -0
- package/dist/types/sdk/broker/session-index.d.ts +12 -0
- package/dist/types/sdk/broker/state-version.d.ts +2 -1
- package/dist/types/sdk/bus/index.d.ts +2 -0
- package/dist/types/sdk/session.d.ts +2 -0
- package/dist/types/tools/ask.d.ts +39 -12
- package/dist/types/tools/browser/screenshot-format.d.ts +8 -0
- package/dist/types/tools/browser/tab-protocol.d.ts +11 -0
- package/dist/types/tools/browser/tab-supervisor.d.ts +1 -1
- package/dist/types/tools/browser/tab-worker-path-resolver.d.ts +2 -0
- package/dist/types/tools/browser/tab-worker-smoke.d.ts +16 -0
- package/dist/types/tools/path-utils.d.ts +9 -14
- package/dist/types/tools/read.d.ts +2 -0
- package/dist/types/tools/render-utils.d.ts +1 -8
- package/dist/types/tools/resource-gc.d.ts +14 -0
- package/dist/types/tools/subagent.d.ts +1 -0
- package/dist/types/utils/file-mentions.d.ts +3 -3
- package/dist/types/utils/image-loading.d.ts +23 -0
- package/dist/types/utils/image-resize.d.ts +2 -0
- package/dist/types/utils/pasted-image-loading.d.ts +39 -0
- package/dist/types/utils/pasted-image-path.d.ts +21 -19
- package/dist/types/web/scrapers/html-to-markdown.d.ts +5 -0
- package/dist/types/web/scrapers/types.d.ts +1 -5
- package/package.json +24 -8
- package/scripts/run-threat-model-check.ts +60 -0
- package/src/cli/launch-disposition.ts +42 -0
- package/src/cli/worktree-cli.ts +9 -287
- package/src/cli.ts +10 -9
- package/src/commands/gc.ts +11 -1
- package/src/commands/sdk.ts +3 -2
- package/src/commands/worktree.ts +10 -53
- package/src/config/model-profiles.ts +15 -15
- package/src/config/settings-schema.ts +55 -10
- package/src/eval/js/shared/runtime.ts +1 -1
- package/src/gjc-runtime/gc-render.ts +19 -1
- package/src/gjc-runtime/gc-runtime.ts +68 -3
- package/src/gjc-runtime/state-argv.ts +292 -0
- package/src/gjc-runtime/state-runtime.ts +51 -212
- package/src/main.ts +99 -22
- package/src/modes/components/custom-editor.ts +103 -32
- package/src/modes/components/custom-provider-wizard.ts +26 -1
- package/src/modes/components/session-observer-overlay.ts +10 -23
- package/src/modes/components/tool-transcript-format.ts +72 -0
- package/src/modes/components/transcript-viewer-overlay.ts +42 -2
- package/src/modes/controllers/input-controller.ts +98 -37
- package/src/modes/controllers/selector-controller.ts +1 -1
- package/src/modes/interactive-mode.ts +13 -2
- package/src/modes/prompt-action-autocomplete.ts +7 -0
- package/src/sdk/broker/broker.ts +139 -32
- package/src/sdk/broker/discovery.ts +125 -0
- package/src/sdk/broker/ensure.ts +103 -19
- package/src/sdk/broker/internal.ts +10 -0
- package/src/sdk/broker/session-index.ts +228 -15
- package/src/sdk/broker/state-version.ts +5 -2
- package/src/sdk/bus/index.ts +4 -1
- package/src/sdk/bus/telegram-daemon.ts +50 -10
- package/src/sdk/host/host.ts +13 -1
- package/src/sdk/session.ts +4 -1
- package/src/session/internal/managed-session-scope.ts +15 -1
- package/src/tools/ask.ts +216 -41
- package/src/tools/bash-allowed-prefixes.ts +79 -29
- package/src/tools/browser/launch.ts +3 -1
- package/src/tools/browser/readable.ts +1 -1
- package/src/tools/browser/screenshot-format.ts +34 -0
- package/src/tools/browser/tab-protocol.ts +3 -0
- package/src/tools/browser/tab-supervisor.ts +125 -122
- package/src/tools/browser/tab-worker-entry.ts +33 -14
- package/src/tools/browser/tab-worker-path-resolver.ts +67 -0
- package/src/tools/browser/tab-worker-smoke.ts +87 -0
- package/src/tools/browser/tab-worker.ts +7 -5
- package/src/tools/output-meta.ts +6 -2
- package/src/tools/path-utils.ts +42 -35
- package/src/tools/read.ts +189 -64
- package/src/tools/render-utils.ts +1 -27
- package/src/tools/resource-gc.ts +123 -34
- package/src/tools/subagent.ts +43 -2
- package/src/utils/file-mentions.ts +4 -4
- package/src/utils/image-loading.ts +85 -34
- package/src/utils/image-resize.ts +16 -9
- package/src/utils/pasted-image-loading.ts +514 -0
- package/src/utils/pasted-image-path.ts +223 -98
- package/src/web/scrapers/html-to-markdown.ts +68 -0
- package/src/web/scrapers/readthedocs.ts +1 -1
- package/src/web/scrapers/types.ts +1 -68
- package/src/web/search/providers/openai-compatible.ts +76 -23
|
@@ -13,6 +13,10 @@
|
|
|
13
13
|
* - Dry-run by default: nothing is deleted unless `--prune`/`--force`.
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
+
import { getAgentDir } from "@gajae-code/utils";
|
|
17
|
+
import { SessionIndex } from "../sdk/broker/session-index";
|
|
18
|
+
import { UnsupportedStateVersionError } from "../sdk/broker/state-version";
|
|
19
|
+
|
|
16
20
|
import { buildGcReportText } from "./gc-render";
|
|
17
21
|
|
|
18
22
|
export type GcStore = "harness_leases" | "team_workers" | "file_locks" | "tmux_sessions" | "registry_entries";
|
|
@@ -115,11 +119,21 @@ export interface GcCounts {
|
|
|
115
119
|
>;
|
|
116
120
|
}
|
|
117
121
|
|
|
122
|
+
export interface GcSessionIndexHealth {
|
|
123
|
+
status: "healthy" | "corrupt" | "repaired" | "unsupported" | "repair_failed";
|
|
124
|
+
valid_prefix_seq: number;
|
|
125
|
+
snapshot_seq?: number;
|
|
126
|
+
reason?: string;
|
|
127
|
+
quarantine_path?: string;
|
|
128
|
+
}
|
|
129
|
+
|
|
118
130
|
export interface GcReport {
|
|
119
131
|
dry_run: boolean;
|
|
132
|
+
operation?: "dry_run" | "prune" | "repair_session_index";
|
|
120
133
|
stores: Record<GcStore, GcRecord[]>;
|
|
121
134
|
counts: GcCounts;
|
|
122
135
|
errors: GcError[];
|
|
136
|
+
session_index?: GcSessionIndexHealth;
|
|
123
137
|
}
|
|
124
138
|
|
|
125
139
|
export interface GcRunResult {
|
|
@@ -225,6 +239,7 @@ function computeCounts(stores: Record<GcStore, GcRecord[]>, errors: GcError[]):
|
|
|
225
239
|
interface ParsedGcArgs {
|
|
226
240
|
json: boolean;
|
|
227
241
|
prune: boolean;
|
|
242
|
+
repairSessionIndex: boolean;
|
|
228
243
|
help: boolean;
|
|
229
244
|
}
|
|
230
245
|
|
|
@@ -233,6 +248,8 @@ class GcUsageError extends Error {}
|
|
|
233
248
|
function parseGcArgs(argv: string[]): ParsedGcArgs {
|
|
234
249
|
let json = false;
|
|
235
250
|
let prune = false;
|
|
251
|
+
let repairSessionIndex = false;
|
|
252
|
+
|
|
236
253
|
let dryRun = false;
|
|
237
254
|
let help = false;
|
|
238
255
|
for (const arg of argv) {
|
|
@@ -245,6 +262,10 @@ function parseGcArgs(argv: string[]): ParsedGcArgs {
|
|
|
245
262
|
case "--force":
|
|
246
263
|
prune = true;
|
|
247
264
|
break;
|
|
265
|
+
case "--repair-session-index":
|
|
266
|
+
repairSessionIndex = true;
|
|
267
|
+
break;
|
|
268
|
+
|
|
248
269
|
case "--dry-run":
|
|
249
270
|
dryRun = true;
|
|
250
271
|
break;
|
|
@@ -256,9 +277,11 @@ function parseGcArgs(argv: string[]): ParsedGcArgs {
|
|
|
256
277
|
throw new GcUsageError(`unknown_flag:${arg}`);
|
|
257
278
|
}
|
|
258
279
|
}
|
|
280
|
+
if (repairSessionIndex && prune) throw new GcUsageError("repair_session_index_cannot_combine_with_prune");
|
|
281
|
+
if (repairSessionIndex && dryRun) throw new GcUsageError("repair_session_index_cannot_combine_with_dry_run");
|
|
259
282
|
// Explicit --dry-run always wins over --prune/--force.
|
|
260
283
|
if (dryRun) prune = false;
|
|
261
|
-
return { json, prune, help };
|
|
284
|
+
return { json, prune, repairSessionIndex, help };
|
|
262
285
|
}
|
|
263
286
|
|
|
264
287
|
/**
|
|
@@ -336,6 +359,40 @@ export function computeExitCode(report: GcReport): number {
|
|
|
336
359
|
return 0;
|
|
337
360
|
}
|
|
338
361
|
|
|
362
|
+
function resolveGcAgentDir(env: NodeJS.ProcessEnv): string {
|
|
363
|
+
return env.GJC_CODING_AGENT_DIR?.trim() || env.PI_CODING_AGENT_DIR?.trim() || getAgentDir();
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
async function collectSessionIndexHealth(repair: boolean, agentDir: string): Promise<GcSessionIndexHealth> {
|
|
367
|
+
const index = new SessionIndex(agentDir);
|
|
368
|
+
try {
|
|
369
|
+
if (repair) {
|
|
370
|
+
const result = await index.repair();
|
|
371
|
+
return {
|
|
372
|
+
status: result.status === "unsupported" ? "unsupported" : result.repaired ? "repaired" : "healthy",
|
|
373
|
+
valid_prefix_seq: result.validPrefixSeq,
|
|
374
|
+
snapshot_seq: result.snapshotSeq,
|
|
375
|
+
...(result.reason ? { reason: result.reason } : {}),
|
|
376
|
+
...(result.quarantinePath ? { quarantine_path: result.quarantinePath } : {}),
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
const diagnosis = await index.diagnose();
|
|
380
|
+
return {
|
|
381
|
+
status: diagnosis.status,
|
|
382
|
+
valid_prefix_seq: diagnosis.validPrefixSeq,
|
|
383
|
+
snapshot_seq: diagnosis.snapshotSeq,
|
|
384
|
+
...(diagnosis.reason ? { reason: diagnosis.reason } : {}),
|
|
385
|
+
};
|
|
386
|
+
} catch (error) {
|
|
387
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
388
|
+
return {
|
|
389
|
+
status: error instanceof UnsupportedStateVersionError ? "unsupported" : "repair_failed",
|
|
390
|
+
valid_prefix_seq: 0,
|
|
391
|
+
reason,
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
339
396
|
export async function runGjcGcCommand(
|
|
340
397
|
argv: string[],
|
|
341
398
|
cwd: string = process.cwd(),
|
|
@@ -357,7 +414,13 @@ export async function runGjcGcCommand(
|
|
|
357
414
|
const resolvedAdapters = adapters ?? (await defaultGcAdapters());
|
|
358
415
|
const ctx: GcContext = { probe: gcPidProbe, force: parsed.prune, env, cwd };
|
|
359
416
|
const report = await collectGcReport(resolvedAdapters, ctx, parsed.prune);
|
|
360
|
-
|
|
417
|
+
report.operation = parsed.repairSessionIndex ? "repair_session_index" : parsed.prune ? "prune" : "dry_run";
|
|
418
|
+
report.session_index = await collectSessionIndexHealth(parsed.repairSessionIndex, resolveGcAgentDir(env));
|
|
419
|
+
const sessionIndexFailed =
|
|
420
|
+
report.session_index?.status === "corrupt" ||
|
|
421
|
+
report.session_index?.status === "unsupported" ||
|
|
422
|
+
report.session_index?.status === "repair_failed";
|
|
423
|
+
const status = sessionIndexFailed ? 1 : computeExitCode(report);
|
|
361
424
|
const stdout = parsed.json ? `${JSON.stringify(report, null, 2)}\n` : buildGcReportText(report);
|
|
362
425
|
return { stdout, stderr: "", status };
|
|
363
426
|
}
|
|
@@ -367,12 +430,14 @@ export function gcHelpText(): string {
|
|
|
367
430
|
"gjc gc - garbage-collect stale GJC session/PID records",
|
|
368
431
|
"",
|
|
369
432
|
"USAGE",
|
|
370
|
-
" $ gjc gc [--prune|--force] [--json]",
|
|
433
|
+
" $ gjc gc [--prune|--force] [--repair-session-index] [--json]",
|
|
434
|
+
|
|
371
435
|
"",
|
|
372
436
|
"FLAGS",
|
|
373
437
|
" --prune, --force Actually remove stale records (default: dry-run report only)",
|
|
374
438
|
" --dry-run Force report-only mode (overrides --prune/--force)",
|
|
375
439
|
" -j, --json Emit machine-readable JSON",
|
|
440
|
+
" --repair-session-index Explicitly quarantine a corrupt session-index suffix and retain its valid prefix",
|
|
376
441
|
"",
|
|
377
442
|
"Liveness-only: a record is removed only when its owning process is dead",
|
|
378
443
|
"(ESRCH). Live / permission-denied / unknown processes are always kept.",
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
import { CANONICAL_GJC_WORKFLOW_SKILLS, type CanonicalGjcWorkflowSkill } from "../skill-state/canonical-skills";
|
|
2
|
+
import { typedArgsFor } from "./workflow-manifest";
|
|
3
|
+
|
|
4
|
+
export type StateAction =
|
|
5
|
+
| "read"
|
|
6
|
+
| "write"
|
|
7
|
+
| "clear"
|
|
8
|
+
| "contract"
|
|
9
|
+
| "handoff"
|
|
10
|
+
| "graph"
|
|
11
|
+
| "prune"
|
|
12
|
+
| "gc"
|
|
13
|
+
| "migrate"
|
|
14
|
+
| "status"
|
|
15
|
+
| "doctor";
|
|
16
|
+
|
|
17
|
+
export const STATE_ACTION_NAMES: ReadonlySet<StateAction> = new Set([
|
|
18
|
+
"read",
|
|
19
|
+
"write",
|
|
20
|
+
"clear",
|
|
21
|
+
"contract",
|
|
22
|
+
"handoff",
|
|
23
|
+
"graph",
|
|
24
|
+
"prune",
|
|
25
|
+
"gc",
|
|
26
|
+
"migrate",
|
|
27
|
+
"status",
|
|
28
|
+
"doctor",
|
|
29
|
+
]);
|
|
30
|
+
|
|
31
|
+
export const STATE_FLAGS_WITH_VALUES: ReadonlySet<string> = new Set([
|
|
32
|
+
"--input",
|
|
33
|
+
"--mode",
|
|
34
|
+
"--session-id",
|
|
35
|
+
"--thread-id",
|
|
36
|
+
"--turn-id",
|
|
37
|
+
"--to",
|
|
38
|
+
"--skill",
|
|
39
|
+
"--format",
|
|
40
|
+
"--older-than",
|
|
41
|
+
"--status",
|
|
42
|
+
"--fields",
|
|
43
|
+
"--since",
|
|
44
|
+
"--limit",
|
|
45
|
+
]);
|
|
46
|
+
|
|
47
|
+
export const STATE_BOOLEAN_FLAGS: ReadonlySet<string> = new Set([
|
|
48
|
+
"--json",
|
|
49
|
+
"--replace",
|
|
50
|
+
"--hard",
|
|
51
|
+
"--dry-run",
|
|
52
|
+
"--migrate",
|
|
53
|
+
"--compact",
|
|
54
|
+
"--history",
|
|
55
|
+
"--force",
|
|
56
|
+
]);
|
|
57
|
+
|
|
58
|
+
export interface StateFlagOccurrence {
|
|
59
|
+
name: string;
|
|
60
|
+
index: number;
|
|
61
|
+
value?: string;
|
|
62
|
+
arity: "boolean" | "value";
|
|
63
|
+
form: "separate" | "attached";
|
|
64
|
+
malformed: boolean;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface StateSelectorCandidate {
|
|
68
|
+
source: "mode" | "positional" | "input.mode" | "input.skill";
|
|
69
|
+
value: string | undefined;
|
|
70
|
+
index: number;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export type StateInputKind = "empty" | "inline" | "file" | "invalid";
|
|
74
|
+
|
|
75
|
+
export interface StateInputClassification {
|
|
76
|
+
index: number;
|
|
77
|
+
raw: string;
|
|
78
|
+
kind: StateInputKind;
|
|
79
|
+
selectors: StateSelectorCandidate[];
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface StateArgvClassification {
|
|
83
|
+
argv: readonly string[];
|
|
84
|
+
action: StateAction;
|
|
85
|
+
effectiveAction: StateAction;
|
|
86
|
+
positionalSkill?: string;
|
|
87
|
+
positionals: readonly string[];
|
|
88
|
+
flags: readonly StateFlagOccurrence[];
|
|
89
|
+
unknownFlags: readonly string[];
|
|
90
|
+
inputs: readonly StateInputClassification[];
|
|
91
|
+
runtimeSelectorCandidates: readonly StateSelectorCandidate[];
|
|
92
|
+
selectorCandidates: readonly StateSelectorCandidate[];
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function normalizedFlagName(arg: string): string | undefined {
|
|
96
|
+
if (!arg.startsWith("--")) return undefined;
|
|
97
|
+
const equalsIndex = arg.indexOf("=");
|
|
98
|
+
return equalsIndex < 0 ? arg : arg.slice(0, equalsIndex);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function normalizedSelector(value: unknown): string | undefined {
|
|
102
|
+
return typeof value === "string" ? value.trim() || undefined : undefined;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
interface StatePositional {
|
|
106
|
+
value: string;
|
|
107
|
+
index: number;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function parseRuntimePositionals(argv: readonly string[]): StatePositional[] {
|
|
111
|
+
const positionals: StatePositional[] = [];
|
|
112
|
+
let skipNext = false;
|
|
113
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
114
|
+
const arg = argv[index]!;
|
|
115
|
+
if (skipNext) {
|
|
116
|
+
skipNext = false;
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
if (STATE_FLAGS_WITH_VALUES.has(arg)) {
|
|
120
|
+
skipNext = true;
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
if (!arg.startsWith("-")) positionals.push({ value: arg, index });
|
|
124
|
+
}
|
|
125
|
+
return positionals;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function parseAction(positionals: readonly StatePositional[]): {
|
|
129
|
+
action: StateAction;
|
|
130
|
+
positionalSkill?: string;
|
|
131
|
+
positionalSkillIndex: number;
|
|
132
|
+
} {
|
|
133
|
+
const [first, second] = positionals;
|
|
134
|
+
if (first?.value && STATE_ACTION_NAMES.has(first.value as StateAction)) {
|
|
135
|
+
return {
|
|
136
|
+
action: first.value as StateAction,
|
|
137
|
+
...(second?.value ? { positionalSkill: second.value } : {}),
|
|
138
|
+
positionalSkillIndex: second?.value ? second.index : -1,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
if (first?.value && second?.value && STATE_ACTION_NAMES.has(second.value as StateAction)) {
|
|
142
|
+
return { action: second.value as StateAction, positionalSkill: first.value, positionalSkillIndex: first.index };
|
|
143
|
+
}
|
|
144
|
+
if (first?.value && !second?.value) {
|
|
145
|
+
return { action: "read", positionalSkill: first.value, positionalSkillIndex: first.index };
|
|
146
|
+
}
|
|
147
|
+
return { action: "read", positionalSkillIndex: -1 };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function stateFlagArities(
|
|
151
|
+
action: StateAction,
|
|
152
|
+
positionalSkill: string | undefined,
|
|
153
|
+
): ReadonlyMap<string, "boolean" | "value"> {
|
|
154
|
+
const arities = new Map<string, "boolean" | "value">();
|
|
155
|
+
for (const flag of STATE_FLAGS_WITH_VALUES) arities.set(flag, "value");
|
|
156
|
+
for (const flag of STATE_BOOLEAN_FLAGS) arities.set(flag, "boolean");
|
|
157
|
+
|
|
158
|
+
const skills =
|
|
159
|
+
positionalSkill && CANONICAL_GJC_WORKFLOW_SKILLS.includes(positionalSkill as CanonicalGjcWorkflowSkill)
|
|
160
|
+
? [positionalSkill as CanonicalGjcWorkflowSkill]
|
|
161
|
+
: CANONICAL_GJC_WORKFLOW_SKILLS;
|
|
162
|
+
for (const skill of skills) {
|
|
163
|
+
for (const arg of typedArgsFor(skill, action)) {
|
|
164
|
+
const name = `--${arg.name}`;
|
|
165
|
+
if (!arities.has(name)) arities.set(name, arg.type === "boolean" ? "boolean" : "value");
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return arities;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function classifyInput(occurrence: StateFlagOccurrence): StateInputClassification {
|
|
172
|
+
const raw = occurrence.value ?? "";
|
|
173
|
+
const trimmed = raw.trim();
|
|
174
|
+
if (!trimmed) return { index: occurrence.index, raw, kind: "empty", selectors: [] };
|
|
175
|
+
if (trimmed.startsWith("@")) return { index: occurrence.index, raw, kind: "file", selectors: [] };
|
|
176
|
+
try {
|
|
177
|
+
const payload = JSON.parse(trimmed) as unknown;
|
|
178
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
|
|
179
|
+
return { index: occurrence.index, raw, kind: "invalid", selectors: [] };
|
|
180
|
+
}
|
|
181
|
+
const record = payload as Record<string, unknown>;
|
|
182
|
+
return {
|
|
183
|
+
index: occurrence.index,
|
|
184
|
+
raw,
|
|
185
|
+
kind: "inline",
|
|
186
|
+
selectors: [
|
|
187
|
+
{ source: "input.mode", value: normalizedSelector(record.mode), index: occurrence.index },
|
|
188
|
+
{ source: "input.skill", value: normalizedSelector(record.skill), index: occurrence.index },
|
|
189
|
+
],
|
|
190
|
+
};
|
|
191
|
+
} catch {
|
|
192
|
+
return { index: occurrence.index, raw, kind: "invalid", selectors: [] };
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export function firstStateFlagValue(classification: StateArgvClassification, name: string): string | undefined {
|
|
197
|
+
const index = classification.argv.indexOf(name);
|
|
198
|
+
return index < 0 ? undefined : classification.argv[index + 1];
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export function classifyStateArgv(argv: readonly string[]): StateArgvClassification {
|
|
202
|
+
const preservedArgv = [...argv];
|
|
203
|
+
const positionalEntries = parseRuntimePositionals(preservedArgv);
|
|
204
|
+
const positionals = positionalEntries.map(positional => positional.value);
|
|
205
|
+
const { action, positionalSkill, positionalSkillIndex } = parseAction(positionalEntries);
|
|
206
|
+
const flagArities = stateFlagArities(action, positionalSkill);
|
|
207
|
+
const flags: StateFlagOccurrence[] = [];
|
|
208
|
+
const unknownFlags: string[] = [];
|
|
209
|
+
|
|
210
|
+
for (let index = 0; index < preservedArgv.length; index += 1) {
|
|
211
|
+
const arg = preservedArgv[index]!;
|
|
212
|
+
const name = normalizedFlagName(arg);
|
|
213
|
+
if (!name) continue;
|
|
214
|
+
const arity = flagArities.get(name);
|
|
215
|
+
if (!arity) {
|
|
216
|
+
unknownFlags.push(name);
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
if (arg !== name) {
|
|
220
|
+
flags.push({
|
|
221
|
+
name,
|
|
222
|
+
index,
|
|
223
|
+
value: arg.slice(name.length + 1),
|
|
224
|
+
arity,
|
|
225
|
+
form: "attached",
|
|
226
|
+
malformed: true,
|
|
227
|
+
});
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
if (arity === "boolean") {
|
|
231
|
+
flags.push({ name, index, arity, form: "separate", malformed: false });
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
const value = preservedArgv[index + 1];
|
|
235
|
+
flags.push({
|
|
236
|
+
name,
|
|
237
|
+
index,
|
|
238
|
+
...(value === undefined ? {} : { value }),
|
|
239
|
+
arity,
|
|
240
|
+
form: "separate",
|
|
241
|
+
malformed: value === undefined || value.startsWith("--"),
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
const effectiveAction = action === "read" && preservedArgv.includes("--migrate") ? "migrate" : action;
|
|
245
|
+
|
|
246
|
+
const modeFlags = flags.filter(flag => flag.name === "--mode" && flag.form === "separate");
|
|
247
|
+
const inputFlags = flags.filter(flag => flag.name === "--input" && flag.form === "separate");
|
|
248
|
+
const inputs = inputFlags.map(classifyInput);
|
|
249
|
+
const positionalCandidate: StateSelectorCandidate = {
|
|
250
|
+
source: "positional",
|
|
251
|
+
value: normalizedSelector(positionalSkill),
|
|
252
|
+
index: positionalSkillIndex,
|
|
253
|
+
};
|
|
254
|
+
const modeCandidates = modeFlags.map(flag => ({
|
|
255
|
+
source: "mode" as const,
|
|
256
|
+
value: normalizedSelector(flag.value),
|
|
257
|
+
index: flag.index,
|
|
258
|
+
}));
|
|
259
|
+
const selectorCandidates = [positionalCandidate, ...modeCandidates, ...inputs.flatMap(input => input.selectors)];
|
|
260
|
+
const firstModeIndex = preservedArgv.indexOf("--mode");
|
|
261
|
+
const firstModeCandidate: StateSelectorCandidate = {
|
|
262
|
+
source: "mode",
|
|
263
|
+
value: normalizedSelector(firstModeIndex < 0 ? undefined : preservedArgv[firstModeIndex + 1]),
|
|
264
|
+
index: firstModeIndex,
|
|
265
|
+
};
|
|
266
|
+
const firstInputIndex = preservedArgv.indexOf("--input");
|
|
267
|
+
const firstInput =
|
|
268
|
+
firstInputIndex < 0
|
|
269
|
+
? undefined
|
|
270
|
+
: classifyInput({
|
|
271
|
+
name: "--input",
|
|
272
|
+
index: firstInputIndex,
|
|
273
|
+
value: preservedArgv[firstInputIndex + 1],
|
|
274
|
+
arity: "value",
|
|
275
|
+
form: "separate",
|
|
276
|
+
malformed: false,
|
|
277
|
+
});
|
|
278
|
+
const runtimeSelectorCandidates = [firstModeCandidate, positionalCandidate, ...(firstInput?.selectors ?? [])];
|
|
279
|
+
|
|
280
|
+
return {
|
|
281
|
+
argv: preservedArgv,
|
|
282
|
+
action,
|
|
283
|
+
effectiveAction,
|
|
284
|
+
...(positionalSkill === undefined ? {} : { positionalSkill }),
|
|
285
|
+
positionals,
|
|
286
|
+
flags,
|
|
287
|
+
unknownFlags,
|
|
288
|
+
inputs,
|
|
289
|
+
runtimeSelectorCandidates,
|
|
290
|
+
selectorCandidates,
|
|
291
|
+
};
|
|
292
|
+
}
|