gcf-aif-ext 0.2.4 → 0.2.5
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/README.md +2 -0
- package/commands/gcf.js +53 -2
- package/extension.json +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -31,6 +31,8 @@ ai-factory gcf hooks-install # arms the pre-commit gate
|
|
|
31
31
|
|
|
32
32
|
`gcf init` is idempotent: it creates what is missing and overwrites nothing.
|
|
33
33
|
|
|
34
|
+
It then offers the optional [harness](../gcf-harness-aif-ext) — orchestration and a readset guard for **Claude Code only** — and on yes runs `ai-factory extension add gcf-harness-aif-ext` and `ai-factory gcf-harness init`. It asks only in a terminal and only when `.ai-factory.json` lists the Claude Code agent; an agent or CI run is never asked and installs nothing. `--harness` installs it without asking, `--no-harness` skips the question.
|
|
35
|
+
|
|
34
36
|
`hooks-install` is a separate step because an AI Factory manifest can declare skills, commands and injections but cannot install a git hook — and because arming a gate that can break your commits is your call, not the extension's. It needs a git repository, and it will not overwrite a `pre-commit` it did not write: if husky or lefthook already owns the hook, it prints the one line to chain from it instead. Without the hook GCF still works; nothing enforces it at commit time.
|
|
35
37
|
|
|
36
38
|
Then `/gcf-vision` for a brand-new game, `/gcf-adopt` for a project that already has code, `/gcf-ingest <gdd.md>` for an existing design doc, or `/gcf-card` to start a feature by hand.
|
package/commands/gcf.js
CHANGED
|
@@ -15,12 +15,53 @@ const MARKER = 'gcf-aif-ext pre-commit gate';
|
|
|
15
15
|
|
|
16
16
|
const out = (json, value, human) => console.log(json ? JSON.stringify(value, null, 2) : human(value));
|
|
17
17
|
|
|
18
|
+
const HARNESS = 'gcf-harness-aif-ext';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* `--harness` / `--no-harness` decide. Without either, ask only when someone can answer
|
|
22
|
+
* and the answer matters: an agent, CI or MinTTY run has no terminal, and a project
|
|
23
|
+
* without the Claude Code agent has no use for a Claude-Code-only extension.
|
|
24
|
+
*/
|
|
25
|
+
export async function harnessWanted(flag, ask, canAsk) {
|
|
26
|
+
if (flag !== undefined) return flag;
|
|
27
|
+
return canAsk ? ask() : false;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function claudeConfigured() {
|
|
31
|
+
try {
|
|
32
|
+
const { agents } = JSON.parse(await readFile(path.join(process.cwd(), '.ai-factory.json'), 'utf8'));
|
|
33
|
+
return agents.some((a) => a?.id === 'claude');
|
|
34
|
+
} catch {
|
|
35
|
+
return true; // unknown config shape: let the human decide
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function askHarness() {
|
|
40
|
+
const { createInterface } = await import('node:readline/promises');
|
|
41
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
42
|
+
try {
|
|
43
|
+
const answer = await rl.question(`Also install ${HARNESS} (orchestration + readset guard, Claude Code only)? [y/N] `);
|
|
44
|
+
return /^y(es)?$/i.test(answer.trim());
|
|
45
|
+
} finally {
|
|
46
|
+
rl.close();
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Re-enter the AIF CLI that is running this command, not whatever `ai-factory` PATH holds
|
|
51
|
+
// (an npx run has none on PATH).
|
|
52
|
+
function aif(args) {
|
|
53
|
+
const res = spawnSync(process.execPath, [process.argv[1], ...args], { cwd: process.cwd(), stdio: 'inherit' });
|
|
54
|
+
if (res.status !== 0) throw new Error(`ai-factory ${args.join(' ')} failed — gcf/ is ready; retry that command by hand`);
|
|
55
|
+
}
|
|
56
|
+
|
|
18
57
|
export function register(program) {
|
|
19
58
|
const gcf = program.command('gcf').description('Game Context Framework core');
|
|
20
59
|
|
|
21
60
|
gcf.command('init')
|
|
22
|
-
.description('Create the gcf/ skeleton from templates (idempotent, never overwrites)')
|
|
23
|
-
.
|
|
61
|
+
.description('Create the gcf/ skeleton from templates (idempotent, never overwrites); offers the harness')
|
|
62
|
+
.option('--harness', `also install ${HARNESS} and run gcf-harness init (Claude Code only)`)
|
|
63
|
+
.option('--no-harness', 'do not offer the harness')
|
|
64
|
+
.action(async (opts) => {
|
|
24
65
|
const root = path.join(process.cwd(), 'gcf');
|
|
25
66
|
for (const dir of ['cards', 'systems', 'source', '.metrics']) {
|
|
26
67
|
await mkdir(path.join(root, dir), { recursive: true });
|
|
@@ -44,6 +85,16 @@ export function register(program) {
|
|
|
44
85
|
const { writeIndex } = await lib('index-gen');
|
|
45
86
|
await writeIndex(root);
|
|
46
87
|
console.log('gcf/ ready. Edit PILLARS.md, then add candidates to backlog.md.');
|
|
88
|
+
|
|
89
|
+
const installed = program.commands?.some((c) => (typeof c?.name === 'function' ? c.name() : c?.name) === 'gcf-harness');
|
|
90
|
+
if (installed) {
|
|
91
|
+
if (opts.harness) aif(['gcf-harness', 'init']);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const canAsk = Boolean(process.stdin.isTTY && process.stdout.isTTY) && (await claudeConfigured());
|
|
95
|
+
if (!(await harnessWanted(opts.harness, askHarness, canAsk))) return;
|
|
96
|
+
aif(['extension', 'add', HARNESS]);
|
|
97
|
+
aif(['gcf-harness', 'init']);
|
|
47
98
|
});
|
|
48
99
|
|
|
49
100
|
gcf.command('index')
|
package/extension.json
CHANGED