@codedology/code-forge 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +64 -0
- package/LICENSE +21 -0
- package/README.md +127 -0
- package/bin/code-forge.mjs +145 -0
- package/docs/README.md +37 -0
- package/docs/reference/blocks.json +288 -0
- package/docs/reference/config.md +162 -0
- package/examples/first-use/README.md +11 -0
- package/examples/first-use/clamp.acceptance.yml +8 -0
- package/examples/first-use/clamp.brief.md +16 -0
- package/examples/first-use/coder/src/math.mjs +31 -0
- package/examples/first-use/coder/test/clamp.test.mjs +26 -0
- package/examples/node-lib/.env.example +3 -0
- package/examples/node-lib/.gitattributes +3 -0
- package/examples/node-lib/.github/workflows/test.yml +11 -0
- package/examples/node-lib/CHANGELOG.md +13 -0
- package/examples/node-lib/README.md +17 -0
- package/examples/node-lib/gitignore +3 -0
- package/examples/node-lib/package.json +19 -0
- package/examples/node-lib/src/list.mjs +23 -0
- package/examples/node-lib/src/math.mjs +21 -0
- package/examples/node-lib/src/strings.mjs +34 -0
- package/examples/node-lib/test/list.test.mjs +12 -0
- package/examples/node-lib/test/math.test.mjs +13 -0
- package/examples/node-lib/test/package.test.mjs +18 -0
- package/examples/node-lib/test/strings.test.mjs +28 -0
- package/package.json +52 -0
- package/schema/code-forge.schema.json +331 -0
- package/schema/facts.schema.json +28 -0
- package/scripts/first-use.mjs +517 -0
- package/scripts/gen-config-doc.mjs +241 -0
- package/skill/SKILL.md +79 -0
- package/skill/references/adapters/claude-code.md +19 -0
- package/skill/references/adapters/solo.md +34 -0
- package/skill/references/adapters/subprocess.md +15 -0
- package/skill/references/code.md +64 -0
- package/skill/references/continuity.md +31 -0
- package/skill/references/decisions.md +50 -0
- package/skill/references/degraded.md +40 -0
- package/skill/references/harden.md +26 -0
- package/skill/references/ledger.md +23 -0
- package/skill/references/plan.md +43 -0
- package/skill/references/proof.md +34 -0
- package/skill/references/review.md +42 -0
- package/skill/references/rules-core.md +72 -0
- package/skill/references/security.md +29 -0
- package/skill/scripts/forge +40 -0
- package/skill/templates/block-record.md +19 -0
- package/skill/templates/facts-sheet.md +22 -0
- package/skill/templates/plan.md +53 -0
- package/src/cli/author.mjs +87 -0
- package/src/cli/block.mjs +262 -0
- package/src/cli/doctor.mjs +74 -0
- package/src/cli/facts.mjs +124 -0
- package/src/cli/gates.mjs +280 -0
- package/src/cli/help.mjs +48 -0
- package/src/cli/init.mjs +14 -0
- package/src/cli/jev.mjs +175 -0
- package/src/cli/keys.mjs +170 -0
- package/src/cli/ledger.mjs +160 -0
- package/src/cli/list.mjs +68 -0
- package/src/cli/models.mjs +131 -0
- package/src/cli/plan.mjs +70 -0
- package/src/cli/proof.mjs +316 -0
- package/src/cli/remove.mjs +76 -0
- package/src/cli/report.mjs +73 -0
- package/src/cli/resolve.mjs +79 -0
- package/src/cli/review-file.mjs +152 -0
- package/src/cli/run.mjs +159 -0
- package/src/cli/s2.mjs +93 -0
- package/src/cli/spawn.mjs +104 -0
- package/src/cli/upgrade.mjs +103 -0
- package/src/cli/validate.mjs +108 -0
- package/src/cli/version.mjs +18 -0
- package/src/cli/worker.mjs +76 -0
- package/src/config/defaults/anthropic.mjs +18 -0
- package/src/config/defaults/index.mjs +24 -0
- package/src/config/defaults/openai.mjs +16 -0
- package/src/config/defaults/xai.mjs +18 -0
- package/src/config/known-ids.mjs +171 -0
- package/src/config/load.mjs +135 -0
- package/src/config/migrate.mjs +46 -0
- package/src/config/refresh.mjs +276 -0
- package/src/config/schema-compile.mjs +211 -0
- package/src/config/schema-paths.mjs +84 -0
- package/src/config/secret-patterns.mjs +85 -0
- package/src/config/validate.mjs +694 -0
- package/src/decide/author-loop.mjs +90 -0
- package/src/decide/escalation.mjs +262 -0
- package/src/decide/fallback-rules.mjs +172 -0
- package/src/decide/jev-client.mjs +216 -0
- package/src/decide/questions.mjs +161 -0
- package/src/decide/thresholds.mjs +199 -0
- package/src/doctor/index.mjs +105 -0
- package/src/doctor/local.mjs +162 -0
- package/src/doctor/probes.mjs +358 -0
- package/src/doctor/rows.mjs +51 -0
- package/src/doctor/worker-probe.mjs +125 -0
- package/src/engines/alias-map.mjs +78 -0
- package/src/engines/builders/claude.mjs +224 -0
- package/src/engines/builders/codex.mjs +242 -0
- package/src/engines/builders/grok.mjs +132 -0
- package/src/engines/builders/index.mjs +35 -0
- package/src/engines/builders/validate-params.mjs +34 -0
- package/src/engines/codex-home.mjs +230 -0
- package/src/engines/isolation.mjs +84 -0
- package/src/engines/probe.mjs +116 -0
- package/src/engines/provider-cli.mjs +24 -0
- package/src/engines/render-rules.mjs +41 -0
- package/src/engines/sentinel.mjs +133 -0
- package/src/engines/usage-parse.mjs +149 -0
- package/src/gates/acceptance.mjs +95 -0
- package/src/gates/detect.mjs +187 -0
- package/src/gates/invariants.mjs +43 -0
- package/src/gates/run.mjs +72 -0
- package/src/gates/safe-edit.mjs +266 -0
- package/src/gates/scope.mjs +154 -0
- package/src/gates/secret-scan.mjs +79 -0
- package/src/gates/transcript-grep.mjs +27 -0
- package/src/install/agent-env.mjs +76 -0
- package/src/install/detect.mjs +121 -0
- package/src/install/harnesses.mjs +131 -0
- package/src/install/link.mjs +463 -0
- package/src/install/wizard/answers.mjs +389 -0
- package/src/install/wizard/flags.mjs +248 -0
- package/src/install/wizard/project-file.mjs +128 -0
- package/src/install/wizard/run.mjs +366 -0
- package/src/install/wizard/steps.mjs +201 -0
- package/src/keys/backends/constants.mjs +7 -0
- package/src/keys/backends/file.mjs +99 -0
- package/src/keys/backends/keychain.mjs +81 -0
- package/src/keys/backends/security-cli.mjs +105 -0
- package/src/keys/onepassword.mjs +46 -0
- package/src/keys/store.mjs +347 -0
- package/src/ledger/calibration.mjs +109 -0
- package/src/ledger/export.mjs +52 -0
- package/src/ledger/outcome.mjs +397 -0
- package/src/ledger/paths.mjs +30 -0
- package/src/ledger/prices.mjs +49 -0
- package/src/ledger/report.mjs +250 -0
- package/src/ledger/write.mjs +279 -0
- package/src/proof/export.mjs +358 -0
- package/src/proof/lock.mjs +138 -0
- package/src/proof/red-green.mjs +519 -0
- package/src/proof/red-parse.mjs +103 -0
- package/src/proof/tiers.mjs +62 -0
- package/src/review/budget.mjs +241 -0
- package/src/review/context.mjs +156 -0
- package/src/review/engine.mjs +264 -0
- package/src/review/finding.schema.json +47 -0
- package/src/review/fixloop.mjs +467 -0
- package/src/review/gate-check.mjs +249 -0
- package/src/review/lenses/A.md +22 -0
- package/src/review/lenses/B.md +23 -0
- package/src/review/lenses/full.md +23 -0
- package/src/review/lenses/judge.md +21 -0
- package/src/review/lenses/quick.md +21 -0
- package/src/review/lenses/recheck.md +23 -0
- package/src/review/packet.mjs +355 -0
- package/src/review/triage.mjs +149 -0
- package/src/review/validate-review.mjs +70 -0
- package/src/session/author.mjs +228 -0
- package/src/session/facts.mjs +855 -0
- package/src/session/plan-check.mjs +294 -0
- package/src/session/s2.mjs +164 -0
- package/src/session/spawn.mjs +610 -0
- package/src/state/block.mjs +289 -0
- package/src/state/cli-args.mjs +67 -0
- package/src/state/paths.mjs +92 -0
- package/src/state/registry.mjs +189 -0
- package/src/state/run.mjs +266 -0
- package/src/state/signer.mjs +132 -0
- package/src/util/exec.mjs +315 -0
- package/src/util/forbidden.mjs +787 -0
- package/src/util/git.mjs +381 -0
- package/src/util/log.mjs +100 -0
- package/src/util/reaper.mjs +300 -0
- package/src/util/redact.mjs +212 -0
- package/src/util/tmp.mjs +409 -0
- package/src/worker/engine.mjs +338 -0
- package/src/worker/loop.mjs +392 -0
- package/src/worker/queue.mjs +186 -0
- package/src/worker/review-state.mjs +145 -0
- package/src/worker/ticket.mjs +195 -0
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `code-forge ledger calibration|outcome|tail` (plan §6.3, C12). Every subcommand takes
|
|
3
|
+
* `--slug <slug>` explicitly: this block (B6) depends only on B0, so it has no config loader
|
|
4
|
+
* (B1) to read `project.slug` from — a later block's verb passes `--slug` through from config.
|
|
5
|
+
*
|
|
6
|
+
* ledger calibration --slug <slug> [--question <id>]
|
|
7
|
+
* ledger outcome --scan-git --slug <slug> [--cwd <dir>] [--days <n>]
|
|
8
|
+
* ledger outcome --pr <n> --ci red|green|reverted --slug <slug>
|
|
9
|
+
* ledger tail --slug <slug> [--n <count>]
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { writeSafe } from '../util/redact.mjs';
|
|
13
|
+
import { buildCalibration } from '../ledger/calibration.mjs';
|
|
14
|
+
import { recordCiOutcome, reviewedEntriesFromRows, scanGitAndRecord } from '../ledger/outcome.mjs';
|
|
15
|
+
import { readAllRows } from '../ledger/write.mjs';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @param {string[]} args @param {string} flag
|
|
19
|
+
* @returns {{ok: true, value: string|undefined} | {ok: false}} `ok: false` means the flag WAS
|
|
20
|
+
* given but with no usable value (missing, or the "next token" is itself another flag) — the
|
|
21
|
+
* caller must treat this as a usage error, not silently fall back to a default. Without this
|
|
22
|
+
* distinction, `ledger outcome --scan-git --slug s --cwd --days 3` would read `--cwd`'s value as
|
|
23
|
+
* absent and quietly scan `process.cwd()` instead — the wrong repo, exiting 0.
|
|
24
|
+
*/
|
|
25
|
+
function readOptionalFlag(args, flag) {
|
|
26
|
+
const i = args.indexOf(flag);
|
|
27
|
+
if (i < 0) return { ok: true, value: undefined };
|
|
28
|
+
const next = args[i + 1];
|
|
29
|
+
if (next === undefined || next.startsWith('--')) return { ok: false };
|
|
30
|
+
return { ok: true, value: next };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @param {string[]} args @param {string} flag @returns {string|undefined}
|
|
35
|
+
* For REQUIRED flags only (`--slug`, `--pr`, `--ci`): a missing value is indistinguishable from a
|
|
36
|
+
* missing flag, and both already fall through to the same "required flag absent" usage error at
|
|
37
|
+
* the call site — unlike the optional flags above, there is no silent-default to protect against.
|
|
38
|
+
*/
|
|
39
|
+
function flagValue(args, flag) {
|
|
40
|
+
const result = readOptionalFlag(args, flag);
|
|
41
|
+
return result.ok ? result.value : undefined;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** @param {string} raw @returns {number|null} a non-negative integer, or null if `raw` isn't one. */
|
|
45
|
+
function parseNonNegativeInt(raw) {
|
|
46
|
+
if (!/^\d+$/.test(raw)) return null;
|
|
47
|
+
return Number.parseInt(raw, 10);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* @param {string[]} args
|
|
52
|
+
* @param {{stdout?: {write: (s: string) => unknown}, stderr?: {write: (s: string) => unknown}}} [deps]
|
|
53
|
+
* @returns {Promise<number>}
|
|
54
|
+
*/
|
|
55
|
+
export async function runLedger(args, deps = {}) {
|
|
56
|
+
const { stdout = process.stdout, stderr = process.stderr } = deps;
|
|
57
|
+
const out = (/** @type {string} */ s) => writeSafe(stdout, s);
|
|
58
|
+
const err = (/** @type {string} */ s) => writeSafe(stderr, s);
|
|
59
|
+
const usage = () => err('usage: code-forge ledger calibration|outcome|tail --slug <slug> [...]\n');
|
|
60
|
+
|
|
61
|
+
const [sub] = args;
|
|
62
|
+
const slug = flagValue(args, '--slug');
|
|
63
|
+
if (!slug) {
|
|
64
|
+
usage();
|
|
65
|
+
return 2;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
if (sub === 'calibration') {
|
|
70
|
+
const question = readOptionalFlag(args, '--question');
|
|
71
|
+
if (!question.ok) {
|
|
72
|
+
err('ledger calibration: --question requires a value\n');
|
|
73
|
+
return 2;
|
|
74
|
+
}
|
|
75
|
+
const rows = await readAllRows(slug);
|
|
76
|
+
const answers = rows.filter((r) => r.event === 'decision' && typeof r.confidence === 'number');
|
|
77
|
+
const outcomes = rows.filter((r) => r.event === 'outcome' && r.decision_id);
|
|
78
|
+
const { buckets, unbacked, unmapped } = buildCalibration(answers, outcomes, { questionId: question.value });
|
|
79
|
+
out(`${JSON.stringify({ buckets, unbacked, unmapped }, null, 2)}\n`);
|
|
80
|
+
return 0;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (sub === 'outcome' && args.includes('--scan-git')) {
|
|
84
|
+
const cwdFlag = readOptionalFlag(args, '--cwd');
|
|
85
|
+
if (!cwdFlag.ok) {
|
|
86
|
+
err('ledger outcome --scan-git: --cwd requires a value\n');
|
|
87
|
+
return 2;
|
|
88
|
+
}
|
|
89
|
+
const cwd = cwdFlag.value ?? process.cwd();
|
|
90
|
+
|
|
91
|
+
const daysFlag = readOptionalFlag(args, '--days');
|
|
92
|
+
if (!daysFlag.ok) {
|
|
93
|
+
err('ledger outcome --scan-git: --days requires a value\n');
|
|
94
|
+
return 2;
|
|
95
|
+
}
|
|
96
|
+
let days;
|
|
97
|
+
if (daysFlag.value !== undefined) {
|
|
98
|
+
days = Number(daysFlag.value);
|
|
99
|
+
if (!Number.isFinite(days) || days <= 0) {
|
|
100
|
+
err(`ledger outcome --scan-git: --days must be a finite number > 0, got "${daysFlag.value}"\n`);
|
|
101
|
+
return 2;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
const rows = await readAllRows(slug);
|
|
105
|
+
const reviewed = reviewedEntriesFromRows(rows);
|
|
106
|
+
const written = await scanGitAndRecord({ cwd, reviewed, days, slug });
|
|
107
|
+
out(`scanned git: ${written.length} row(s) marked reverted\n`);
|
|
108
|
+
return 0;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (sub === 'outcome' && flagValue(args, '--pr') && flagValue(args, '--ci')) {
|
|
112
|
+
const prArg = flagValue(args, '--pr');
|
|
113
|
+
const pr = parseNonNegativeInt(prArg);
|
|
114
|
+
if (pr === null || pr === 0) {
|
|
115
|
+
err(`ledger outcome: --pr must be a positive integer, got "${prArg}"\n`);
|
|
116
|
+
return 2;
|
|
117
|
+
}
|
|
118
|
+
const ci = flagValue(args, '--ci');
|
|
119
|
+
if (!['red', 'green', 'reverted'].includes(ci)) {
|
|
120
|
+
err(`ledger outcome: --ci must be red|green|reverted, got "${ci}"\n`);
|
|
121
|
+
return 2;
|
|
122
|
+
}
|
|
123
|
+
await recordCiOutcome({ pr, ci: /** @type {'red'|'green'|'reverted'} */ (ci), slug });
|
|
124
|
+
out(`recorded outcome for PR #${pr}: ${ci}\n`);
|
|
125
|
+
return 0;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (sub === 'tail') {
|
|
129
|
+
const nFlag = readOptionalFlag(args, '--n');
|
|
130
|
+
if (!nFlag.ok) {
|
|
131
|
+
err('ledger tail: --n requires a value\n');
|
|
132
|
+
return 2;
|
|
133
|
+
}
|
|
134
|
+
const nArg = nFlag.value ?? '20';
|
|
135
|
+
const n = parseNonNegativeInt(nArg);
|
|
136
|
+
if (n === null) {
|
|
137
|
+
err(`ledger tail: --n must be a non-negative integer, got "${nArg}"\n`);
|
|
138
|
+
return 2;
|
|
139
|
+
}
|
|
140
|
+
const rows = await readAllRows(slug);
|
|
141
|
+
for (const row of n === 0 ? [] : rows.slice(-n)) {
|
|
142
|
+
out(`${JSON.stringify(row)}\n`);
|
|
143
|
+
}
|
|
144
|
+
return 0;
|
|
145
|
+
}
|
|
146
|
+
} catch (thrown) {
|
|
147
|
+
// Same problem `report.mjs` fixed in round 1: a corrupt ledger, an unwritable folder, or
|
|
148
|
+
// `--scan-git` outside a git repo must not leak a raw stack trace past the redaction path.
|
|
149
|
+
err(`ledger ${sub}: ${thrown?.message ?? String(thrown)}\n`);
|
|
150
|
+
return 1;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
usage();
|
|
154
|
+
return 2;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** @param {string[]} args @returns {Promise<number>} */
|
|
158
|
+
export default async function ledger(args) {
|
|
159
|
+
return runLedger(args);
|
|
160
|
+
}
|
package/src/cli/list.mjs
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `code-forge list` (plan §2.1: "`code-forge list` prints what is linked where and whether the
|
|
3
|
+
* target resolves"). Two sections:
|
|
4
|
+
* - INSTALLED — every recorded row of `~/.code-forge/installs.json`, plus a live `resolves`
|
|
5
|
+
* check (a broken symlink, or a target someone deleted by hand, reports `no`).
|
|
6
|
+
* - HARNESSES — the full path table (plan §2.1), with live PATH/home-dir detection and each
|
|
7
|
+
* row's `verified` flag from `src/install/harnesses.mjs` (the Gemini row is `UNVERIFIED`).
|
|
8
|
+
*
|
|
9
|
+
* Takes no arguments; `code-forge list <anything>` is a usage error.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import os from 'node:os';
|
|
13
|
+
import { writeSafe } from '../util/redact.mjs';
|
|
14
|
+
import { detectAll } from '../install/detect.mjs';
|
|
15
|
+
import { HARNESSES } from '../install/harnesses.mjs';
|
|
16
|
+
import { installsPath, readInstalls, targetResolves } from '../install/link.mjs';
|
|
17
|
+
|
|
18
|
+
const USAGE = 'usage: code-forge list\n';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @param {string[]} args
|
|
22
|
+
* @param {object} deps
|
|
23
|
+
* @param {{write: (s: string) => unknown}} [deps.stdout]
|
|
24
|
+
* @param {{write: (s: string) => unknown}} [deps.stderr]
|
|
25
|
+
* @param {string} [deps.home]
|
|
26
|
+
* @param {NodeJS.ProcessEnv} [deps.env]
|
|
27
|
+
* @returns {Promise<number>}
|
|
28
|
+
*/
|
|
29
|
+
export async function runList(args, { stdout = process.stdout, stderr = process.stderr, home = os.homedir(), env = process.env } = {}) {
|
|
30
|
+
const out = (/** @type {string} */ s) => writeSafe(stdout, s);
|
|
31
|
+
const err = (/** @type {string} */ s) => writeSafe(stderr, s);
|
|
32
|
+
|
|
33
|
+
if (args.length > 0) {
|
|
34
|
+
err(USAGE);
|
|
35
|
+
return 2;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const records = await readInstalls(installsPath(home));
|
|
39
|
+
out('INSTALLED\n');
|
|
40
|
+
out('HARNESS\tSCOPE\tMETHOD\tTARGET\tRESOLVES\n');
|
|
41
|
+
for (const record of records) {
|
|
42
|
+
const resolves = await targetResolves(record.target);
|
|
43
|
+
out(`${record.harness}\t${record.scope}\t${record.method}\t${record.target}\t${resolves ? 'yes' : 'no'}\n`);
|
|
44
|
+
}
|
|
45
|
+
if (records.length === 0) {
|
|
46
|
+
out('(none)\n');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const detections = await detectAll({ home, pathEnv: env.PATH });
|
|
50
|
+
out('\nHARNESSES\n');
|
|
51
|
+
out('ID\tLABEL\tDETECTED\tVERIFIED\tPROJECT PATH\n');
|
|
52
|
+
for (const harness of HARNESSES) {
|
|
53
|
+
const detection = detections.find((d) => d.id === harness.id);
|
|
54
|
+
out(
|
|
55
|
+
`${harness.id}\t${harness.label}\t${detection?.detected ? 'yes' : 'no'}\t${harness.verified ? 'yes' : 'UNVERIFIED'}\t${harness.projectSkillsDir}\n`,
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return 0;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* @param {string[]} args
|
|
64
|
+
* @returns {Promise<number>}
|
|
65
|
+
*/
|
|
66
|
+
export default async function list(args) {
|
|
67
|
+
return runList(args, {});
|
|
68
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `code-forge models [--refresh --from-cli-caches]` — lists the model catalog per provider (the
|
|
3
|
+
* shipped `known-ids.mjs` list, plus the project's `known_extra`, plus anything the user-level
|
|
4
|
+
* `seen-in-cache` list has picked up). `--refresh --from-cli-caches` is the opt-in C15 refresh:
|
|
5
|
+
* it reads whichever of `~/.codex/models_cache.json` / `~/.grok/models_cache.json` exist and
|
|
6
|
+
* merges their ids into the user-level cache BEFORE listing — it never writes the project's
|
|
7
|
+
* `.code-forge.yml` (plan §1.3, §2 step 3).
|
|
8
|
+
*
|
|
9
|
+
* Every line printed from file-derived text passes `maskSecretTokens` (and B0's `redact` inside
|
|
10
|
+
* `log`): a Grok cache stores an `api_key` next to every model id, and a project config is where
|
|
11
|
+
* a user pastes a key by mistake.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { KNOWN_IDS, knownIdsForProvider } from '../config/known-ids.mjs';
|
|
15
|
+
import { loadProjectConfig } from '../config/load.mjs';
|
|
16
|
+
import { loadSeenInCache, refreshFromCliCaches } from '../config/refresh.mjs';
|
|
17
|
+
import { maskSecretTokens } from '../config/secret-patterns.mjs';
|
|
18
|
+
import * as log from '../util/log.mjs';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @param {string[]} args
|
|
22
|
+
* @returns {{refresh: boolean, fromCliCaches: boolean} | {error: string}}
|
|
23
|
+
*/
|
|
24
|
+
function parseArgs(args) {
|
|
25
|
+
let refresh = false;
|
|
26
|
+
let fromCliCaches = false;
|
|
27
|
+
for (const arg of args) {
|
|
28
|
+
if (arg === '--refresh') {
|
|
29
|
+
refresh = true;
|
|
30
|
+
} else if (arg === '--from-cli-caches') {
|
|
31
|
+
fromCliCaches = true;
|
|
32
|
+
} else {
|
|
33
|
+
return { error: `unknown argument: ${arg}` };
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
if (fromCliCaches && !refresh) {
|
|
37
|
+
return { error: '--from-cli-caches requires --refresh' };
|
|
38
|
+
}
|
|
39
|
+
return { refresh, fromCliCaches };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* An OWN entry of a map as a string array — anything else (a number, a string, an inherited
|
|
44
|
+
* `constructor`) is `[]`, so iterating it can never throw or split a string into characters.
|
|
45
|
+
* @param {unknown} map
|
|
46
|
+
* @param {string} key
|
|
47
|
+
* @returns {string[]}
|
|
48
|
+
*/
|
|
49
|
+
function ownIdList(map, key) {
|
|
50
|
+
if (!map || typeof map !== 'object' || !Object.hasOwn(map, key)) {
|
|
51
|
+
return [];
|
|
52
|
+
}
|
|
53
|
+
const value = /** @type {Record<string, unknown>} */ (map)[key];
|
|
54
|
+
return Array.isArray(value) ? value.filter((id) => typeof id === 'string') : [];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* @param {string[]} args
|
|
59
|
+
* @returns {Promise<number>}
|
|
60
|
+
*/
|
|
61
|
+
export default async function models(args) {
|
|
62
|
+
const parsed = parseArgs(args);
|
|
63
|
+
if ('error' in parsed) {
|
|
64
|
+
log.error(maskSecretTokens(parsed.error));
|
|
65
|
+
return 2;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (parsed.refresh) {
|
|
69
|
+
if (!parsed.fromCliCaches) {
|
|
70
|
+
// C15: `--refresh` ships as an opt-in gated by `--from-cli-caches` — there is no other
|
|
71
|
+
// refresh source in 0.1, so a bare `--refresh` is a usage error, not a silent no-op.
|
|
72
|
+
log.error('models --refresh requires --from-cli-caches (the only refresh source in 0.1)');
|
|
73
|
+
return 2;
|
|
74
|
+
}
|
|
75
|
+
let result;
|
|
76
|
+
try {
|
|
77
|
+
result = await refreshFromCliCaches();
|
|
78
|
+
} catch (err) {
|
|
79
|
+
log.error(maskSecretTokens(`refresh failed: ${/** @type {Error} */ (err).message}`));
|
|
80
|
+
return 1;
|
|
81
|
+
}
|
|
82
|
+
if (result.sourcesRead.length === 0) {
|
|
83
|
+
// 0 sources read is NOT a refresh — never claim success while doing nothing useful.
|
|
84
|
+
log.warn('no CLI caches were found to refresh from (checked ~/.codex and ~/.grok)');
|
|
85
|
+
} else {
|
|
86
|
+
log.info(
|
|
87
|
+
`refreshed from ${result.sourcesRead.length} CLI cache(s) -> ${result.cachePath} (status: seen-in-cache)`,
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
for (const skipped of result.sourcesSkipped) {
|
|
91
|
+
log.warn(maskSecretTokens(`skipped ${skipped.path}: ${skipped.reason}`));
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const seenInCache = await loadSeenInCache();
|
|
96
|
+
const projectLoad = await loadProjectConfig();
|
|
97
|
+
if (!projectLoad.ok && projectLoad.error !== 'not-found') {
|
|
98
|
+
// A project config that exists but fails to load is reported (a missing one stays silent) —
|
|
99
|
+
// otherwise the user's known_extra silently vanishes from the listing.
|
|
100
|
+
log.warn(maskSecretTokens(`could not read known_extra from ${projectLoad.path}: ${projectLoad.error}: ${projectLoad.message}`));
|
|
101
|
+
}
|
|
102
|
+
const rawKnownExtra = projectLoad.ok ? projectLoad.config.known_extra : undefined;
|
|
103
|
+
const knownExtra = rawKnownExtra && typeof rawKnownExtra === 'object' && !Array.isArray(rawKnownExtra) ? rawKnownExtra : {};
|
|
104
|
+
|
|
105
|
+
// The union of every provider that appears in ANY of the three sources, not just
|
|
106
|
+
// `Object.keys(KNOWN_IDS)` — a provider that only exists in known_extra or seen-in-cache is
|
|
107
|
+
// still listed, instead of silently vanishing from the output.
|
|
108
|
+
const allProviders = new Set([...Object.keys(KNOWN_IDS), ...Object.keys(knownExtra), ...Object.keys(seenInCache)]);
|
|
109
|
+
|
|
110
|
+
for (const provider of [...allProviders].sort()) {
|
|
111
|
+
log.info(maskSecretTokens(`${provider}:`));
|
|
112
|
+
// One `printed` set per provider so an id shipped in BOTH the catalog and known_extra/
|
|
113
|
+
// seen-in-cache is printed once, tagged by its highest-precedence source.
|
|
114
|
+
const printed = new Set();
|
|
115
|
+
for (const id of knownIdsForProvider(provider)) {
|
|
116
|
+
log.info(` ${id}`);
|
|
117
|
+
printed.add(id);
|
|
118
|
+
}
|
|
119
|
+
for (const id of ownIdList(knownExtra, provider)) {
|
|
120
|
+
if (printed.has(id)) continue;
|
|
121
|
+
log.info(maskSecretTokens(` ${id} (known_extra)`));
|
|
122
|
+
printed.add(id);
|
|
123
|
+
}
|
|
124
|
+
for (const id of ownIdList(seenInCache, provider)) {
|
|
125
|
+
if (printed.has(id)) continue;
|
|
126
|
+
log.info(maskSecretTokens(` ${id} (seen-in-cache)`));
|
|
127
|
+
printed.add(id);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return 0;
|
|
131
|
+
}
|
package/src/cli/plan.mjs
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `code-forge plan check <plan-file> [--facts <sheet>]` (plan §3.8 rule 4, §3.2; block B9b) —
|
|
3
|
+
* the deterministic harden exit. Prints every failing row on stderr, one per line, and exits 1
|
|
4
|
+
* when there is any; prints `plan check: ok (<n> blocks)` and exits 0 otherwise; 2 on usage.
|
|
5
|
+
* Limits come from `.code-forge.yml` when there is one (`caps.coders`, `budget.block_cases`,
|
|
6
|
+
* `budget.block_lines`), else the plan defaults.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { readFileSync } from 'node:fs';
|
|
10
|
+
import path from 'node:path';
|
|
11
|
+
import { loadProjectConfig } from '../config/load.mjs';
|
|
12
|
+
import { checkPlan } from '../session/plan-check.mjs';
|
|
13
|
+
import { parseFlags } from '../state/cli-args.mjs';
|
|
14
|
+
import { StateError } from '../state/paths.mjs';
|
|
15
|
+
import { writeSafe } from '../util/redact.mjs';
|
|
16
|
+
|
|
17
|
+
const USAGE = 'usage: code-forge plan check <plan-file> [--facts <sheet>]\n';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @param {string[]} args
|
|
21
|
+
* @param {{stdout?: {write: (s: string) => unknown}, stderr?: {write: (s: string) => unknown}, cwd?: string}} [deps]
|
|
22
|
+
* @returns {Promise<number>}
|
|
23
|
+
*/
|
|
24
|
+
export async function runPlanVerb(args, deps = {}) {
|
|
25
|
+
const stdout = deps.stdout ?? process.stdout;
|
|
26
|
+
const stderr = deps.stderr ?? process.stderr;
|
|
27
|
+
const cwd = deps.cwd ?? process.cwd();
|
|
28
|
+
try {
|
|
29
|
+
const { flags, positionals } = parseFlags(args, { values: ['facts'] });
|
|
30
|
+
if (positionals.length !== 2 || positionals[0] !== 'check') {
|
|
31
|
+
writeSafe(stderr, USAGE);
|
|
32
|
+
return 2;
|
|
33
|
+
}
|
|
34
|
+
const planPath = path.resolve(cwd, positionals[1]);
|
|
35
|
+
const factsPath = typeof flags.facts === 'string' ? path.resolve(cwd, flags.facts) : undefined;
|
|
36
|
+
/** @param {string} file @param {string} what */
|
|
37
|
+
const read = (file, what) => {
|
|
38
|
+
try {
|
|
39
|
+
return readFileSync(file, 'utf8');
|
|
40
|
+
} catch {
|
|
41
|
+
throw new StateError('usage', `${what} cannot be read`);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
const text = read(planPath, 'the plan file');
|
|
45
|
+
const factsText = factsPath ? read(factsPath, '--facts') : undefined;
|
|
46
|
+
const loaded = await loadProjectConfig(cwd);
|
|
47
|
+
if (!loaded.ok && loaded.error !== 'not-found') {
|
|
48
|
+
writeSafe(stderr, `plan: ${loaded.message}\n`);
|
|
49
|
+
return 2;
|
|
50
|
+
}
|
|
51
|
+
const result = checkPlan(text, { planPath, factsPath, factsText, cfg: loaded.ok ? loaded.config : undefined });
|
|
52
|
+
if (!result.ok) {
|
|
53
|
+
for (const line of result.errors) writeSafe(stderr, `${line}\n`);
|
|
54
|
+
return 1;
|
|
55
|
+
}
|
|
56
|
+
writeSafe(stdout, `plan check: ok (${result.blocks} blocks)\n`);
|
|
57
|
+
return 0;
|
|
58
|
+
} catch (err) {
|
|
59
|
+
if (err instanceof StateError) {
|
|
60
|
+
writeSafe(stderr, `plan: ${err.message}\n`);
|
|
61
|
+
return 2;
|
|
62
|
+
}
|
|
63
|
+
throw err;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** @param {string[]} args */
|
|
68
|
+
export default async function planVerb(args) {
|
|
69
|
+
return runPlanVerb(args);
|
|
70
|
+
}
|