create-agdf 0.9.0 → 0.9.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -15,10 +15,10 @@ This file is the living control pointer for active AGDF delivery work. Detailed
15
15
  9. Historical artefacts remain linkable but do not override the current pointer.
16
16
  10. If an item is superseded, mark the replacement.
17
17
  11. If active work grows beyond roughly ten items, triage the backlog.
18
- 12. `Status` must be one of the human-readable labels `create-agdf/bin/create-agdf.js`'s `backlogStatusLabels` normalizes: Needs UR, Awaiting Brownfield Review, Awaiting PRD, Awaiting PRD Approval, Awaiting SD, Awaiting SD Approval, Awaiting TP, Awaiting TP Approval, In Progress, Blocked, Awaiting QA, Awaiting UAT, Completed, Superseded, Abandoned. That map is authoritative; this list only mirrors it for readability.
19
- 13. `Artefacts` link labels must be one of `backlogArtefactLabels`: `ur`, `brownfield`, `prd`, `sd`, `tp`, `qa`, `or` (case-insensitive). Do not invent other labels such as a raw artefact filename.
18
+ 12. `Status` must be one of the human-readable labels normalized by `create-agdf/lib/control-evaluation/shared.js`: Needs UR, Awaiting Brownfield Review, Awaiting PRD, Awaiting PRD Approval, Awaiting SD, Awaiting SD Approval, Awaiting TP, Awaiting TP Approval, In Progress, Blocked, Awaiting QA, Awaiting UAT, Completed, Superseded, Abandoned. The parser module is authoritative; this list only mirrors it for readability.
19
+ 13. `Artefacts` link labels must be one of the labels normalized by the same parser module: `ur`, `brownfield`, `prd`, `sd`, `tp`, `qa`, `or` (case-insensitive). Do not invent other labels such as a raw artefact filename.
20
20
  14. After writing or updating this file, run `doctor --json` (or the locally available equivalent) and resolve any `AGDF_BACKLOG_STATUS_UNKNOWN` or `AGDF_BACKLOG_ARTEFACT_LABEL_UNKNOWN` finding before treating the edit as done.
21
- 15. A `Work item` cell may optionally start with a bracketed scope tag, one of `[framework-maintenance]` or `[external-delivery]`, so the split between AGDF's own maintenance work and external product delivery stays visible at a glance. The tag is never required retroactively on `Completed / Superseded Pointers` rows. `create-agdf/bin/create-agdf.js`'s `backlogScopeLabels` map is authoritative for the two accepted values.
21
+ 15. A `Work item` cell may optionally start with a bracketed scope tag, one of `[framework-maintenance]` or `[external-delivery]`, so the split between AGDF's own maintenance work and external product delivery stays visible at a glance. The tag is never required retroactively on `Completed / Superseded Pointers` rows. `create-agdf/lib/control-evaluation/shared.js` is authoritative for the accepted values.
22
22
  16. After writing or updating this file, also resolve any `AGDF_BACKLOG_SCOPE_LABEL_UNKNOWN` finding, which fires only for a present-but-unrecognized bracketed tag, never for an absent one.
23
23
 
24
24
  ## Active Backlog
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agdf",
3
- "version": "0.9.0",
3
+ "version": "0.9.3",
4
4
  "description": "Operating system for governed AI-assisted delivery.",
5
5
  "author": {
6
6
  "name": "Arndt Gold",
@@ -15,10 +15,10 @@ This file is the living control pointer for active AGDF delivery work. Detailed
15
15
  9. Historical artefacts remain linkable but do not override the current pointer.
16
16
  10. If an item is superseded, mark the replacement.
17
17
  11. If active work grows beyond roughly ten items, triage the backlog.
18
- 12. `Status` must be one of the human-readable labels `create-agdf/bin/create-agdf.js`'s `backlogStatusLabels` normalizes: Needs UR, Awaiting Brownfield Review, Awaiting PRD, Awaiting PRD Approval, Awaiting SD, Awaiting SD Approval, Awaiting TP, Awaiting TP Approval, In Progress, Blocked, Awaiting QA, Awaiting UAT, Completed, Superseded, Abandoned. That map is authoritative; this list only mirrors it for readability.
19
- 13. `Artefacts` link labels must be one of `backlogArtefactLabels`: `ur`, `brownfield`, `prd`, `sd`, `tp`, `qa`, `or` (case-insensitive). Do not invent other labels such as a raw artefact filename.
18
+ 12. `Status` must be one of the human-readable labels normalized by `create-agdf/lib/control-evaluation/shared.js`: Needs UR, Awaiting Brownfield Review, Awaiting PRD, Awaiting PRD Approval, Awaiting SD, Awaiting SD Approval, Awaiting TP, Awaiting TP Approval, In Progress, Blocked, Awaiting QA, Awaiting UAT, Completed, Superseded, Abandoned. The parser module is authoritative; this list only mirrors it for readability.
19
+ 13. `Artefacts` link labels must be one of the labels normalized by the same parser module: `ur`, `brownfield`, `prd`, `sd`, `tp`, `qa`, `or` (case-insensitive). Do not invent other labels such as a raw artefact filename.
20
20
  14. After writing or updating this file, run `doctor --json` (or the locally available equivalent) and resolve any `AGDF_BACKLOG_STATUS_UNKNOWN` or `AGDF_BACKLOG_ARTEFACT_LABEL_UNKNOWN` finding before treating the edit as done.
21
- 15. A `Work item` cell may optionally start with a bracketed scope tag, one of `[framework-maintenance]` or `[external-delivery]`, so the split between AGDF's own maintenance work and external product delivery stays visible at a glance. The tag is never required retroactively on `Completed / Superseded Pointers` rows. `create-agdf/bin/create-agdf.js`'s `backlogScopeLabels` map is authoritative for the two accepted values.
21
+ 15. A `Work item` cell may optionally start with a bracketed scope tag, one of `[framework-maintenance]` or `[external-delivery]`, so the split between AGDF's own maintenance work and external product delivery stays visible at a glance. The tag is never required retroactively on `Completed / Superseded Pointers` rows. `create-agdf/lib/control-evaluation/shared.js` is authoritative for the accepted values.
22
22
  16. After writing or updating this file, also resolve any `AGDF_BACKLOG_SCOPE_LABEL_UNKNOWN` finding, which fires only for a present-but-unrecognized bracketed tag, never for an absent one.
23
23
 
24
24
  ## Active Backlog
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "id": "agdf",
4
- "version": "0.9.0",
4
+ "version": "0.9.3",
5
5
  "displayName": "AI Governance & Delivery Framework",
6
6
  "developerName": "Arndt Gold",
7
7
  "description": "Operating system for governed AI-assisted delivery.",
@@ -0,0 +1,191 @@
1
+ import { join } from "node:path";
2
+ import process from "node:process";
3
+ import {
4
+ createRun,
5
+ migrateLegacy,
6
+ resolveRuns,
7
+ writeLegacyProjection,
8
+ } from "../control-state/index.js";
9
+ import {
10
+ buildStatusCard,
11
+ evaluateGateCheck,
12
+ postApprovalTransition,
13
+ printGateCheckReport,
14
+ } from "../control-evaluation/gate-check.js";
15
+ import {
16
+ evaluateDeliveryMap,
17
+ printDeliveryMapReport,
18
+ } from "../control-evaluation/delivery-map.js";
19
+ import { evaluateDoctor, printDoctorReport } from "../control-evaluation/doctor.js";
20
+ import {
21
+ defaultOpenCodeConfigDir,
22
+ evaluateOpenCodeStatus,
23
+ installOpenCodeGlobalPlugin,
24
+ installOpenCodeGlobalSurface,
25
+ printOpenCodeStatus,
26
+ } from "../installers/opencode.js";
27
+ import {
28
+ installClaudeGlobalPlugin,
29
+ installCodexGlobalPlugin,
30
+ } from "../installers/plugin-installers.js";
31
+ import { agdfFragmentPath, generatedFilesForTarget, openCodeConfigFragmentPath } from "../scaffold/plan.js";
32
+ import { printNextSteps } from "../scaffold/presentation.js";
33
+ import { assertGeneratedWritePlan, removeOwnedLegacyOpenCodeAgents, writeGeneratedFile } from "../scaffold/write.js";
34
+ import { renderUsage, resolveCommand, validateCommandOptions } from "./command-registry.js";
35
+ import { executeDeliveryPathSearch } from "./delivery-path-search-command.js";
36
+ import { CliUsageError, parseArgs } from "./parse-args.js";
37
+
38
+ const deliveryMapDependencies = Object.freeze({
39
+ evaluateDoctor,
40
+ buildStatusCard,
41
+ postApprovalTransition,
42
+ });
43
+
44
+ function createHandlers({ io, env, exec }) {
45
+ const installerAdapters = exec ? { exec, io } : { io };
46
+ const scaffoldHandler = (options) => runScaffold(options, io);
47
+ return new Map([
48
+ ["codex-repo", scaffoldHandler],
49
+ ["copilot", scaffoldHandler],
50
+ ["opencode-repo", scaffoldHandler],
51
+ ["both", scaffoldHandler],
52
+ ["init", scaffoldHandler],
53
+ ["config", scaffoldHandler],
54
+ ["run-create", (options) => {
55
+ io.log(createRun(options.dir, options.runId));
56
+ return 0;
57
+ }],
58
+ ["run-migrate", (options) => {
59
+ io.log(JSON.stringify(migrateLegacy(options.dir, options.runId), null, 2));
60
+ return 0;
61
+ }],
62
+ ["run-render-legacy", (options) => {
63
+ const selected = resolveRuns(options.dir, { runIdArg: options.runId });
64
+ const output = join(options.dir, ".agdf", "control", "AGDF_RUN.md");
65
+ writeLegacyProjection(output, selected.run.path);
66
+ io.log(output);
67
+ return 0;
68
+ }],
69
+ ["doctor", (options) => {
70
+ const report = evaluateDoctor(options.dir, options);
71
+ printDoctorReport(report, options.json, io);
72
+ return report.status === "block" ? 2 : 0;
73
+ }],
74
+ ["gate-check", (options) => {
75
+ const report = evaluateGateCheck(options.dir, options);
76
+ printGateCheckReport(report, options.json, options.statusCard, io);
77
+ return report.status === "blocked" ? 2 : 0;
78
+ }],
79
+ ["delivery-map", (options) => {
80
+ const report = evaluateDeliveryMap(options.dir, options, deliveryMapDependencies);
81
+ printDeliveryMapReport(report, options.json, io);
82
+ return report.status === "block" ? 2 : 0;
83
+ }],
84
+ ["delivery-path-search", async (options) => {
85
+ try {
86
+ const result = await executeDeliveryPathSearch(options, io);
87
+ return result.status === "recommendation" ? 0 : 2;
88
+ } catch (error) {
89
+ io.error(`Delivery Path Search failed: ${error.message}`);
90
+ return 2;
91
+ }
92
+ }],
93
+ ["opencode-status", (options) => {
94
+ const configDir = env.OPENCODE_CONFIG_DIR || defaultOpenCodeConfigDir();
95
+ const report = evaluateOpenCodeStatus(options.dir, configDir);
96
+ printOpenCodeStatus(report, options.json, io);
97
+ return report.status === "configured" ? 0 : 1;
98
+ }],
99
+ ["codex", () => {
100
+ try {
101
+ installCodexGlobalPlugin(installerAdapters);
102
+ io.log("AGDF Codex plugin installed globally.");
103
+ io.log("Run npx --yes @agdf/cli@latest codex-repo in a repository when you want to test AGDF from repository-local plugin files.");
104
+ return 0;
105
+ } catch (error) {
106
+ io.error(error.message);
107
+ return 1;
108
+ }
109
+ }],
110
+ ["claude", () => {
111
+ try {
112
+ installClaudeGlobalPlugin(installerAdapters);
113
+ io.log("AGDF Claude Code plugin installed globally.");
114
+ return 0;
115
+ } catch (error) {
116
+ io.error(error.message);
117
+ return 1;
118
+ }
119
+ }],
120
+ ["opencode", (options) => {
121
+ const configDir = options.dirExplicit ? options.dir : defaultOpenCodeConfigDir();
122
+ try {
123
+ const result = installOpenCodeGlobalPlugin(configDir);
124
+ installOpenCodeGlobalSurface(configDir);
125
+ io.log(`AGDF OpenCode global plugin ${result.added ? "installed" : "already present"}: ${result.configPath}`);
126
+ const report = evaluateOpenCodeStatus(options.dir, configDir, result.transition);
127
+ printOpenCodeStatus(report, false, io);
128
+ io.log("Restart OpenCode so it loads the updated global plugin config.");
129
+ return 0;
130
+ } catch (error) {
131
+ io.error(error.message);
132
+ return 1;
133
+ }
134
+ }],
135
+ ]);
136
+ }
137
+
138
+ function runScaffold(options, io) {
139
+ const files = generatedFilesForTarget(options.target, options.dir, options.force, options.language);
140
+ const wroteAgentsFragment = files.some((file) => file.path === agdfFragmentPath);
141
+ const wroteOpenCodeConfigFragment = files.some((file) => file.path === openCodeConfigFragmentPath);
142
+ let removedOpenCodeAgents = [];
143
+
144
+ try {
145
+ assertGeneratedWritePlan(options.dir, files, options.force);
146
+ for (const file of files) {
147
+ writeGeneratedFile(options.dir, file.path, file.content, options.force, file.allowOverwrite);
148
+ }
149
+ if (options.target === "opencode-repo") removedOpenCodeAgents = removeOwnedLegacyOpenCodeAgents(options.dir);
150
+ } catch (error) {
151
+ io.error(error.message);
152
+ return 1;
153
+ }
154
+
155
+ printNextSteps(options.target, options.dir, files, wroteAgentsFragment, wroteOpenCodeConfigFragment, removedOpenCodeAgents, io);
156
+ return 0;
157
+ }
158
+
159
+ export async function runCli(argv = process.argv.slice(2), adapters = {}) {
160
+ const io = adapters.io ?? console;
161
+ const env = adapters.env ?? process.env;
162
+ let parsed;
163
+ try {
164
+ parsed = parseArgs(argv, adapters.parser);
165
+ } catch (error) {
166
+ if (!(error instanceof CliUsageError)) throw error;
167
+ io.error(error.message);
168
+ if (error.showUsage) io.log(renderUsage());
169
+ return error.exitCode;
170
+ }
171
+
172
+ if (parsed.kind === "help") {
173
+ io.log(renderUsage());
174
+ return 0;
175
+ }
176
+
177
+ let options;
178
+ try {
179
+ options = validateCommandOptions(parsed.options);
180
+ } catch (error) {
181
+ io.error(error instanceof Error ? error.message : String(error));
182
+ return 1;
183
+ }
184
+ const command = resolveCommand(options.target);
185
+ if (!command) throw new Error(`No handler is registered for ${options.target}.`);
186
+ const handler = createHandlers({ io, env, exec: adapters.exec }).get(command.handler);
187
+ if (!handler) throw new Error(`No implementation is registered for ${command.name}.`);
188
+ return await handler(options);
189
+ }
190
+
191
+ export const main = runCli;
@@ -0,0 +1,101 @@
1
+ function command(name, usages) {
2
+ return Object.freeze({
3
+ name,
4
+ handler: name,
5
+ groups: Object.freeze(Object.keys(usages)),
6
+ usages: Object.freeze(Object.fromEntries(
7
+ Object.entries(usages).map(([group, suffixes]) => [group, Object.freeze(suffixes)]),
8
+ )),
9
+ });
10
+ }
11
+
12
+ export const commandRegistry = Object.freeze([
13
+ command("codex", { preferred: [""], scaffold: [""] }),
14
+ command("codex-repo", { preferred: [""], scaffold: [""] }),
15
+ command("claude", { preferred: [""], scaffold: [""] }),
16
+ command("copilot", { scaffold: [""] }),
17
+ command("opencode", { preferred: [""], scaffold: [""] }),
18
+ command("opencode-status", { preferred: [""], scaffold: [""] }),
19
+ command("opencode-repo", { preferred: [""], scaffold: [""] }),
20
+ command("both", { scaffold: [""] }),
21
+ command("init", { preferred: [""], scaffold: [""] }),
22
+ command("config", { scaffold: [" --language de"] }),
23
+ command("doctor", { preferred: [""], scaffold: [""], legacy: [" --json"] }),
24
+ command("gate-check", { preferred: [" --json"], scaffold: [""], legacy: [" --json"] }),
25
+ command("delivery-map", { scaffold: [""] }),
26
+ command("delivery-path-search", {
27
+ preferred: [" --surface codex --json", " --surface claude --json"],
28
+ scaffold: [" --surface codex", " --surface claude"],
29
+ }),
30
+ command("run-create", { preferred: [" --run <run_id>"] }),
31
+ command("run-migrate", { preferred: [" [--run <run_id>]"] }),
32
+ command("run-render-legacy", { preferred: [" --run <run_id>"] }),
33
+ ]);
34
+
35
+ const commandByName = new Map(commandRegistry.map((entry) => [entry.name, entry]));
36
+
37
+ export function resolveCommand(name) {
38
+ return commandByName.get(name) ?? null;
39
+ }
40
+
41
+ export function supportedCommandNames() {
42
+ return commandRegistry.map((entry) => entry.name);
43
+ }
44
+
45
+ export function validateCommandOptions(options) {
46
+ if (options.allActive && !["doctor", "delivery-map"].includes(options.target)) {
47
+ throw new Error("--all-active is supported only by doctor and delivery-map");
48
+ }
49
+ if (options.target === "run-create" && (!options.runId || options.allActive)) {
50
+ throw new Error("run-create requires --run and rejects --all-active");
51
+ }
52
+ if (options.target === "run-render-legacy" && !options.runId) {
53
+ throw new Error("run-render-legacy requires --run");
54
+ }
55
+ return options;
56
+ }
57
+
58
+ function usageLines(group, prefix) {
59
+ return commandRegistry
60
+ .flatMap((entry) => (entry.usages[group] ?? []).map((suffix) => ` ${prefix}${entry.name}${suffix}`))
61
+ .join("\n");
62
+ }
63
+
64
+ export function renderUsage() {
65
+ return `create-agdf
66
+
67
+ Preferred AGDF CLI:
68
+ ${usageLines("preferred", "npx --yes @agdf/cli@latest ")}
69
+
70
+ Scaffold-compatible npm create usage:
71
+ ${usageLines("scaffold", "npm create agdf@latest -- ")}
72
+
73
+ Backward-compatible create-agdf usage:
74
+ ${usageLines("legacy", "npx --yes create-agdf@latest ")}
75
+
76
+ Options:
77
+ --dir <path> Write files into a specific directory. With opencode, use this as the OpenCode config directory.
78
+ --force Overwrite existing generated files
79
+ --language <tag>
80
+ Set AGDF chat and artefact language. Defaults to detected system locale.
81
+ --lang <tag> Alias for --language
82
+ --json Print machine-readable command output as JSON
83
+ --status-card Print compact gate-check status-card output for interactive use
84
+ --run <run_id> Select one canonical run
85
+ --all-active Evaluate every active run (doctor and delivery-map only)
86
+ --surface <codex|claude|copilot|opencode|generic>
87
+ Declare the active Delivery Path Search surface
88
+ --fixture <path>
89
+ Use deterministic evaluator/candidate fixtures instead of a live evaluator
90
+ --persist Persist the redacted Delivery Path Search result under the current scope
91
+ --model <id> Optional Codex evaluator model
92
+ --generate-candidates
93
+ Add one bounded AI-native candidate-generation call before evaluation
94
+ --generator-model <id>
95
+ Optional candidate-generator model override
96
+ --max-generated-candidates <1-5>
97
+ --generation-timeout-ms <1-30000>
98
+ --generation-cost-units <1-5>
99
+ --help Show this help
100
+ `;
101
+ }
@@ -0,0 +1,75 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { searchInputFromControl } from '../delivery-path-search/state-adapter.js';
3
+ import { runDeliveryPathSearch } from '../delivery-path-search/search-engine.js';
4
+ import { codexEvaluator } from '../delivery-path-search/evaluators/codex.js';
5
+ import { claudeEvaluator } from '../delivery-path-search/evaluators/claude.js';
6
+ import { fixtureEvaluator } from '../delivery-path-search/evaluators/protocol.js';
7
+ import { codexGenerator } from '../delivery-path-search/generators/codex.js';
8
+ import { claudeGenerator } from '../delivery-path-search/generators/claude.js';
9
+ import { fixtureGenerator } from '../delivery-path-search/generators/protocol.js';
10
+ import { enforcementForSurface } from '../delivery-path-search/surfaces/capabilities.js';
11
+ import { persistSearchResult } from '../delivery-path-search/persistence.js';
12
+
13
+ export async function executeDeliveryPathSearch(options, io = console) {
14
+ const fixture = options.fixture ? JSON.parse(readFileSync(options.fixture, "utf8")) : null;
15
+ const enforcement = fixture?.input?.enforcement ?? enforcementForSurface(options.surface);
16
+ const fixtureGeneration = fixture?.generator_response;
17
+ const generationEnabled = Boolean(fixtureGeneration || options.generateCandidates);
18
+ const baseInput = fixture?.input ?? searchInputFromControl(options.dir, {
19
+ scopeKey: options.runId,
20
+ enforcement,
21
+ generation: generationEnabled ? {
22
+ enabled: true,
23
+ maxProposals: options.maxGeneratedCandidates,
24
+ maxDurationMs: options.generationTimeoutMs,
25
+ maxCostUnits: options.generationCostUnits,
26
+ } : undefined,
27
+ });
28
+ const input = generationEnabled && !baseInput.generation ? {
29
+ ...baseInput,
30
+ generation: {
31
+ enabled: true,
32
+ max_calls: 1,
33
+ max_proposals: options.maxGeneratedCandidates,
34
+ max_duration_ms: options.generationTimeoutMs,
35
+ max_cost_units: options.generationCostUnits,
36
+ },
37
+ } : baseInput;
38
+ const evaluator = fixture
39
+ ? fixtureEvaluator(fixture.evaluations ?? {})
40
+ : options.surface === "codex"
41
+ ? codexEvaluator({ cwd: options.dir, model: options.model })
42
+ : options.surface === "claude"
43
+ ? claudeEvaluator({ cwd: options.dir, model: options.model })
44
+ : null;
45
+ if (!evaluator) {
46
+ throw new Error(`${options.surface} has no executable evaluator in this release. Codex and Claude are the reference adapters; --fixture is available for deterministic contract testing.`);
47
+ }
48
+ const generator = fixtureGeneration
49
+ ? fixtureGenerator(fixtureGeneration)
50
+ : options.generateCandidates && options.surface === "codex"
51
+ ? codexGenerator({ cwd: options.dir, model: options.generatorModel, timeoutMs: options.generationTimeoutMs })
52
+ : options.generateCandidates && options.surface === "claude"
53
+ ? claudeGenerator({ cwd: options.dir, model: options.generatorModel, timeoutMs: options.generationTimeoutMs })
54
+ : null;
55
+ if (options.generateCandidates && !generator) {
56
+ throw new Error(`${options.surface} has no executable candidate generator; Codex and Claude are tool-enforced reference transports.`);
57
+ }
58
+ const result = await runDeliveryPathSearch(input, evaluator, { candidates: fixture?.candidates, generator });
59
+ if (options.persist) result.persistence = persistSearchResult(options.dir, result);
60
+ if (options.json) {
61
+ io.log(JSON.stringify(result, null, 2));
62
+ return result;
63
+ }
64
+ io.log(`AGDF Delivery Path Search: ${result.status}`);
65
+ io.log(`Current gate: ${result.current_gate}`);
66
+ io.log(`Enforcement: ${result.enforcement.level}`);
67
+ io.log(`Recommendation: ${result.recommendation?.action ?? "none"}`);
68
+ io.log(`Evaluations: ${result.budgets.evaluations}`);
69
+ io.log(`Generation: ${result.generation.status} (${result.generation.accepted}/${result.generation.returned} accepted)`);
70
+ io.log(`Generation budget: ${result.generation.cost_units} cost units, ${result.generation.duration_ms} ms`);
71
+ if (result.generation.failure_code) io.log(`Generation failure: ${result.generation.failure_code}`);
72
+ io.log(`Stopping reason: ${result.stopping_reason}`);
73
+ io.log(result.next_gate_action);
74
+ return result;
75
+ }
@@ -0,0 +1,142 @@
1
+ import { resolve } from "node:path";
2
+ import process from "node:process";
3
+ import { configuredLanguage, resolveLanguagePreference } from "./runtime-context.js";
4
+ import { resolveCommand, supportedCommandNames } from "./command-registry.js";
5
+
6
+ export class CliUsageError extends Error {
7
+ constructor(message, { showUsage = false } = {}) {
8
+ super(message);
9
+ this.name = "CliUsageError";
10
+ this.exitCode = 1;
11
+ this.showUsage = showUsage;
12
+ }
13
+ }
14
+
15
+ function requiredValue(args, index, option) {
16
+ const next = args[index + 1];
17
+ if (!next) throw new CliUsageError(`Missing value for ${option}`);
18
+ return next;
19
+ }
20
+
21
+ export function parseArgs(argv, dependencies = {}) {
22
+ const cwd = dependencies.cwd ?? process.cwd();
23
+ const languagePreference = dependencies.resolveLanguagePreference ?? resolveLanguagePreference;
24
+ const normalizeLanguage = dependencies.configuredLanguage ?? configuredLanguage;
25
+ const args = [...argv];
26
+ let target;
27
+ let dir = ".";
28
+ let force = false;
29
+ let json = false;
30
+ let statusCard = false;
31
+ let language;
32
+ let dirExplicit = false;
33
+ let surface = "generic";
34
+ let fixture;
35
+ let persist = false;
36
+ let model;
37
+ let generateCandidates = false;
38
+ let generatorModel;
39
+ let maxGeneratedCandidates = 5;
40
+ let generationTimeoutMs = 30000;
41
+ let generationCostUnits = 5;
42
+ let runId;
43
+ let allActive = false;
44
+
45
+ for (let i = 0; i < args.length; i += 1) {
46
+ const arg = args[i];
47
+ if (!arg) continue;
48
+ if (arg === "--help" || arg === "-h") return { kind: "help" };
49
+ if (arg === "--force") { force = true; continue; }
50
+ if (arg === "--json") { json = true; continue; }
51
+ if (arg === "--status-card") { statusCard = true; continue; }
52
+ if (arg === "--persist") { persist = true; continue; }
53
+ if (arg === "--generate-candidates") { generateCandidates = true; continue; }
54
+ if (arg === "--all-active") { allActive = true; continue; }
55
+
56
+ if (arg === "--run") {
57
+ runId = requiredValue(args, i, arg);
58
+ i += 1;
59
+ continue;
60
+ }
61
+
62
+ if (["--surface", "--fixture", "--model", "--generator-model", "--max-generated-candidates", "--generation-timeout-ms", "--generation-cost-units"].includes(arg)) {
63
+ const next = requiredValue(args, i, arg);
64
+ if (arg === "--surface") {
65
+ if (!["codex", "claude", "copilot", "opencode", "generic"].includes(next)) {
66
+ throw new CliUsageError("Unsupported surface. Use codex, claude, copilot, opencode or generic.");
67
+ }
68
+ surface = next;
69
+ } else if (arg === "--fixture") fixture = next;
70
+ else if (arg === "--model") model = next;
71
+ else if (arg === "--generator-model") generatorModel = next;
72
+ else {
73
+ const value = Number(next);
74
+ const maximum = arg === "--generation-timeout-ms" ? 30000 : 5;
75
+ if (!Number.isInteger(value) || value < 1 || value > maximum) {
76
+ throw new CliUsageError(`${arg} must be an integer from 1 to ${maximum}.`);
77
+ }
78
+ if (arg === "--max-generated-candidates") maxGeneratedCandidates = value;
79
+ else if (arg === "--generation-timeout-ms") generationTimeoutMs = value;
80
+ else generationCostUnits = value;
81
+ }
82
+ i += 1;
83
+ continue;
84
+ }
85
+
86
+ if (arg === "--language" || arg === "--lang") {
87
+ const next = requiredValue(args, i, arg);
88
+ const normalized = normalizeLanguage(next);
89
+ if (!normalized) throw new CliUsageError("Invalid language tag. Use a BCP 47 tag such as de, en or fr-CA.");
90
+ language = normalized;
91
+ i += 1;
92
+ continue;
93
+ }
94
+
95
+ if (arg === "--dir") {
96
+ dir = requiredValue(args, i, arg);
97
+ dirExplicit = true;
98
+ i += 1;
99
+ continue;
100
+ }
101
+
102
+ if (arg === "--target" || arg === "-t") {
103
+ target = requiredValue(args, i, arg);
104
+ i += 1;
105
+ continue;
106
+ }
107
+
108
+ if (!arg.startsWith("-") && !target) {
109
+ target = arg;
110
+ continue;
111
+ }
112
+ throw new CliUsageError(`Unknown argument: ${arg}`);
113
+ }
114
+
115
+ if (!target || !resolveCommand(target)) {
116
+ throw new CliUsageError(`Please choose one target: ${supportedCommandNames().join(", ")}.`, { showUsage: true });
117
+ }
118
+
119
+ return {
120
+ kind: "command",
121
+ options: {
122
+ target,
123
+ dir: resolve(cwd, dir),
124
+ force,
125
+ json,
126
+ statusCard,
127
+ dirExplicit,
128
+ language: languagePreference(language),
129
+ surface,
130
+ fixture: fixture ? resolve(cwd, fixture) : null,
131
+ persist,
132
+ model,
133
+ generateCandidates,
134
+ runId,
135
+ allActive,
136
+ generatorModel,
137
+ maxGeneratedCandidates,
138
+ generationTimeoutMs,
139
+ generationCostUnits,
140
+ },
141
+ };
142
+ }
@@ -0,0 +1,83 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { dirname, join, resolve } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import process from "node:process";
5
+ import {
6
+ canonicalizeLanguageTag,
7
+ resolvePresentationLocale,
8
+ } from "../interaction-presentation.js";
9
+
10
+ const __dirname = dirname(fileURLToPath(import.meta.url));
11
+
12
+ export const packageRoot = resolve(__dirname, "../..");
13
+ export const generatedRoot = join(packageRoot, "generated");
14
+
15
+ const pluginDefinitionPath = join(generatedRoot, "plugins", "agdf", "meta", "agdf-plugin.definition.json");
16
+ const interactionLocalesPath = join(generatedRoot, "plugins", "agdf", "meta", "agdf-interaction-locales.json");
17
+
18
+ export const pluginDefinition = JSON.parse(readFileSync(pluginDefinitionPath, "utf8"));
19
+ export const interactionLocales = JSON.parse(readFileSync(interactionLocalesPath, "utf8"));
20
+
21
+ export function configuredLanguage(value) {
22
+ const normalized = canonicalizeLanguageTag(value);
23
+ if (!normalized) return "";
24
+ const language = normalized.split("-")[0];
25
+ if (interactionLocales.locales[normalized] || interactionLocales.locales[language]) {
26
+ return resolvePresentationLocale(interactionLocales, normalized);
27
+ }
28
+ return normalized;
29
+ }
30
+
31
+ export function detectSystemLocale(env = process.env) {
32
+ const envLocale = env.LC_ALL || env.LC_MESSAGES || env.LANG || env.LANGUAGE || "";
33
+ if (envLocale) return envLocale;
34
+ try {
35
+ return Intl.DateTimeFormat().resolvedOptions().locale || "";
36
+ } catch {
37
+ return "";
38
+ }
39
+ }
40
+
41
+ export function resolveLanguagePreference(explicitLanguage, env = process.env) {
42
+ const explicit = configuredLanguage(explicitLanguage);
43
+ if (explicit) {
44
+ return {
45
+ artifact_language: explicit,
46
+ chat_language: explicit,
47
+ runtime_language: "en",
48
+ source: "parameter",
49
+ detected_locale: detectSystemLocale(env) || "unknown",
50
+ };
51
+ }
52
+
53
+ const detectedLocale = detectSystemLocale(env);
54
+ const detected = configuredLanguage(detectedLocale) || interactionLocales.fallbackLocale;
55
+ return {
56
+ artifact_language: detected,
57
+ chat_language: detected,
58
+ runtime_language: "en",
59
+ source: detectedLocale ? "system_locale" : "default",
60
+ detected_locale: detectedLocale || "unknown",
61
+ };
62
+ }
63
+
64
+ export function resolveConfiguredChatLanguage(targetDir) {
65
+ const configPath = join(targetDir, ".agdf", "control", "config.json");
66
+ if (!existsSync(configPath)) return interactionLocales.fallbackLocale;
67
+ try {
68
+ const config = JSON.parse(readFileSync(configPath, "utf8"));
69
+ return resolvePresentationLocale(interactionLocales, config.chat_language);
70
+ } catch {
71
+ return interactionLocales.fallbackLocale;
72
+ }
73
+ }
74
+
75
+ export function languageConfigContent(languagePreference) {
76
+ return `${JSON.stringify({
77
+ artifact_language: languagePreference.artifact_language,
78
+ chat_language: languagePreference.chat_language,
79
+ runtime_language: languagePreference.runtime_language,
80
+ source: languagePreference.source,
81
+ detected_locale: languagePreference.detected_locale,
82
+ }, null, 2)}\n`;
83
+ }