infernoflow 0.10.13 → 0.10.15
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/bin/infernoflow.mjs +68 -0
- package/dist/lib/ai/ideDetection.mjs +1 -0
- package/dist/lib/ai/localProvider.mjs +1 -0
- package/dist/lib/ai/providerRouter.mjs +1 -0
- package/dist/lib/commands/adopt.mjs +20 -0
- package/dist/lib/commands/check.mjs +3 -0
- package/dist/lib/commands/context.mjs +20 -0
- package/dist/lib/commands/docGate.mjs +2 -0
- package/dist/lib/commands/implement.mjs +7 -0
- package/dist/lib/commands/init.mjs +17 -0
- package/dist/lib/commands/installCursorHooks.mjs +1 -0
- package/dist/lib/commands/installVsCodeCopilotHooks.mjs +1 -0
- package/dist/lib/commands/prImpact.mjs +2 -0
- package/dist/lib/commands/run.mjs +10 -0
- package/dist/lib/commands/status.mjs +4 -0
- package/dist/lib/commands/suggest.mjs +62 -0
- package/dist/lib/commands/syncAuto.mjs +1 -0
- package/dist/lib/cursorHooksInstall.mjs +1 -0
- package/dist/lib/draftToolingInstall.mjs +8 -0
- package/dist/lib/ui/output.mjs +6 -0
- package/dist/lib/ui/prompts.mjs +6 -0
- package/dist/lib/vsCodeCopilotHooksInstall.mjs +1 -0
- package/{templates → dist/templates}/scripts/inferno-vscode-copilot-hook.mjs +23 -3
- package/package.json +48 -44
- package/bin/infernoflow.mjs +0 -138
- package/lib/ai/ideDetection.mjs +0 -31
- package/lib/ai/localProvider.mjs +0 -88
- package/lib/ai/providerRouter.mjs +0 -73
- package/lib/commands/adopt.mjs +0 -768
- package/lib/commands/check.mjs +0 -179
- package/lib/commands/context.mjs +0 -164
- package/lib/commands/docGate.mjs +0 -81
- package/lib/commands/implement.mjs +0 -103
- package/lib/commands/init.mjs +0 -401
- package/lib/commands/installCursorHooks.mjs +0 -36
- package/lib/commands/installVsCodeCopilotHooks.mjs +0 -37
- package/lib/commands/prImpact.mjs +0 -157
- package/lib/commands/run.mjs +0 -338
- package/lib/commands/status.mjs +0 -172
- package/lib/commands/suggest.mjs +0 -501
- package/lib/commands/syncAuto.mjs +0 -96
- package/lib/cursorHooksInstall.mjs +0 -39
- package/lib/draftToolingInstall.mjs +0 -69
- package/lib/ui/output.mjs +0 -72
- package/lib/ui/prompts.mjs +0 -147
- package/lib/vsCodeCopilotHooksInstall.mjs +0 -42
- /package/{templates → dist/templates}/ci/github-inferno-check.yml +0 -0
- /package/{templates → dist/templates}/cursor/hooks/inferno-session-draft.mjs +0 -0
- /package/{templates → dist/templates}/cursor/hooks.json +0 -0
- /package/{templates → dist/templates}/github-hooks/infernoflow-drafts.json +0 -0
- /package/{templates → dist/templates}/inferno/CHANGELOG.md +0 -0
- /package/{templates → dist/templates}/inferno/capabilities.json +0 -0
- /package/{templates → dist/templates}/inferno/contract.json +0 -0
- /package/{templates → dist/templates}/inferno/scenarios/happy_path.json +0 -0
- /package/{templates → dist/templates}/scripts/inferno-doc-gate.mjs +0 -0
- /package/{templates → dist/templates}/scripts/inferno-install-hooks.mjs +0 -0
- /package/{templates → dist/templates}/scripts/inferno-promote-draft.mjs +0 -0
package/lib/commands/init.mjs
DELETED
|
@@ -1,401 +0,0 @@
|
|
|
1
|
-
import * as fs from "node:fs";
|
|
2
|
-
import * as path from "node:path";
|
|
3
|
-
import * as readline from "node:readline";
|
|
4
|
-
import { fileURLToPath } from "node:url";
|
|
5
|
-
import { header, ok, warn, done, nextSteps, cyan, yellow, gray } from "../ui/output.mjs";
|
|
6
|
-
import {
|
|
7
|
-
discoverProjectSignals,
|
|
8
|
-
reviewCapabilitiesInteractive,
|
|
9
|
-
writeAdoptionBaseline,
|
|
10
|
-
buildAdoptionReport,
|
|
11
|
-
summarizeCapabilities,
|
|
12
|
-
buildSignalsReport,
|
|
13
|
-
} from "./adopt.mjs";
|
|
14
|
-
import { installCursorHooksArtifacts } from "../cursorHooksInstall.mjs";
|
|
15
|
-
import { installVsCodeCopilotHooksArtifacts } from "../vsCodeCopilotHooksInstall.mjs";
|
|
16
|
-
|
|
17
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
18
|
-
|
|
19
|
-
function getTemplatesRoot() {
|
|
20
|
-
return path.resolve(__dirname, "../../templates");
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function ask(rl, question, defaultVal = "") {
|
|
24
|
-
return new Promise(resolve => {
|
|
25
|
-
const hint = defaultVal ? gray(` (${defaultVal})`) : "";
|
|
26
|
-
rl.question(` ${question}${hint}: `, answer => {
|
|
27
|
-
resolve(answer.trim() || defaultVal);
|
|
28
|
-
});
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function getArgValue(args, ...flags) {
|
|
33
|
-
for (const flag of flags) {
|
|
34
|
-
const i = args.indexOf(flag);
|
|
35
|
-
if (i !== -1 && args[i + 1] && !args[i + 1].startsWith("-")) return args[i + 1];
|
|
36
|
-
}
|
|
37
|
-
return null;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function copyFile(src, dst, force, silent = false) {
|
|
41
|
-
if (fs.existsSync(dst) && !force) {
|
|
42
|
-
if (!silent) warn("Skipped (exists): " + path.relative(process.cwd(), dst));
|
|
43
|
-
return false;
|
|
44
|
-
}
|
|
45
|
-
fs.mkdirSync(path.dirname(dst), { recursive: true });
|
|
46
|
-
fs.copyFileSync(src, dst);
|
|
47
|
-
if (!silent) ok("Created: " + cyan(path.relative(process.cwd(), dst)));
|
|
48
|
-
return true;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function copyDirDeep(srcDir, dstDir, force) {
|
|
52
|
-
fs.mkdirSync(dstDir, { recursive: true });
|
|
53
|
-
for (const entry of fs.readdirSync(srcDir, { withFileTypes: true })) {
|
|
54
|
-
const src = path.join(srcDir, entry.name);
|
|
55
|
-
const dst = path.join(dstDir, entry.name);
|
|
56
|
-
if (entry.isDirectory()) copyDirDeep(src, dst, force);
|
|
57
|
-
else copyFile(src, dst, force);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function upsertScripts(cwd, silent = false) {
|
|
62
|
-
const pkgPath = path.join(cwd, "package.json");
|
|
63
|
-
if (!fs.existsSync(pkgPath)) return;
|
|
64
|
-
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
|
|
65
|
-
pkg.scripts = pkg.scripts || {};
|
|
66
|
-
let changed = false;
|
|
67
|
-
const toAdd = {
|
|
68
|
-
"inferno:check": "infernoflow check",
|
|
69
|
-
"inferno:status": "infernoflow status",
|
|
70
|
-
"inferno:gate": "infernoflow doc-gate",
|
|
71
|
-
"inferno:impact": "infernoflow pr-impact --json",
|
|
72
|
-
"inferno:sync": "infernoflow sync --auto --json",
|
|
73
|
-
"inferno:run": "infernoflow run \"sync check\" --provider auto --json",
|
|
74
|
-
"inferno:hooks": "node scripts/inferno-install-hooks.mjs"
|
|
75
|
-
};
|
|
76
|
-
for (const [k, v] of Object.entries(toAdd)) {
|
|
77
|
-
if (!pkg.scripts[k]) { pkg.scripts[k] = v; changed = true; }
|
|
78
|
-
}
|
|
79
|
-
if (changed) {
|
|
80
|
-
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
|
|
81
|
-
if (!silent) ok("Updated " + cyan("package.json") + " scripts");
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
function detectProjectName(cwd) {
|
|
86
|
-
const pkgPath = path.join(cwd, "package.json");
|
|
87
|
-
if (fs.existsSync(pkgPath)) {
|
|
88
|
-
try {
|
|
89
|
-
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
|
|
90
|
-
if (pkg.name) return pkg.name.replace(/[^a-z0-9_-]/gi, "_");
|
|
91
|
-
} catch {}
|
|
92
|
-
}
|
|
93
|
-
return path.basename(cwd);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function writeContract(contractPath, policyId, capabilities) {
|
|
97
|
-
const contract = {
|
|
98
|
-
policyId,
|
|
99
|
-
policyVersion: 1,
|
|
100
|
-
capabilities,
|
|
101
|
-
rules: {
|
|
102
|
-
docsRequiredOnCapabilityChange: true,
|
|
103
|
-
requireScenarioForEachCapability: true,
|
|
104
|
-
requireChangelogOnCapabilityChange: true
|
|
105
|
-
}
|
|
106
|
-
};
|
|
107
|
-
fs.writeFileSync(contractPath, JSON.stringify(contract, null, 2) + "\n");
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
function writeCapabilities(capsPath, capabilities) {
|
|
111
|
-
const registry = {
|
|
112
|
-
schemaVersion: 1,
|
|
113
|
-
capabilities: capabilities.map(id => ({
|
|
114
|
-
id,
|
|
115
|
-
title: id.replace(/([A-Z])/g, " $1").trim(),
|
|
116
|
-
since: "0.1.0"
|
|
117
|
-
}))
|
|
118
|
-
};
|
|
119
|
-
fs.writeFileSync(capsPath, JSON.stringify(registry, null, 2) + "\n");
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
function writeScenario(scenariosDir, capabilities) {
|
|
123
|
-
fs.mkdirSync(scenariosDir, { recursive: true });
|
|
124
|
-
const scenario = {
|
|
125
|
-
scenarioId: "happy_path",
|
|
126
|
-
description: "Basic happy-path flow covering all capabilities",
|
|
127
|
-
capabilitiesCovered: capabilities,
|
|
128
|
-
steps: capabilities.map(c => ({
|
|
129
|
-
action: c,
|
|
130
|
-
expect: `${c} works as expected`
|
|
131
|
-
}))
|
|
132
|
-
};
|
|
133
|
-
fs.writeFileSync(
|
|
134
|
-
path.join(scenariosDir, "happy_path.json"),
|
|
135
|
-
JSON.stringify(scenario, null, 2) + "\n"
|
|
136
|
-
);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
function writeChangelog(changelogPath, policyId) {
|
|
140
|
-
const content = `# Changelog — ${policyId}
|
|
141
|
-
|
|
142
|
-
## Unreleased
|
|
143
|
-
|
|
144
|
-
- Initial capabilities defined
|
|
145
|
-
|
|
146
|
-
## 0.1.0 — Initial release
|
|
147
|
-
|
|
148
|
-
- Project initialized with infernoflow
|
|
149
|
-
`;
|
|
150
|
-
fs.writeFileSync(changelogPath, content);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
export async function initCommand(args) {
|
|
154
|
-
const cwd = process.cwd();
|
|
155
|
-
const force = args.includes("--force") || args.includes("-f");
|
|
156
|
-
const yes = args.includes("--yes") || args.includes("-y");
|
|
157
|
-
const adopt = args.includes("--adopt");
|
|
158
|
-
const cursorHooks = args.includes("--cursor-hooks");
|
|
159
|
-
const vscodeCopilotHooks = args.includes("--vscode-copilot-hooks");
|
|
160
|
-
const reportJson = args.includes("--report-json");
|
|
161
|
-
const reportJsonOnly = args.includes("--report-json-only");
|
|
162
|
-
const reportHumanOnly = args.includes("--report-human-only");
|
|
163
|
-
const langOverride = getArgValue(args, "--lang");
|
|
164
|
-
const frameworkOverride = getArgValue(args, "--framework");
|
|
165
|
-
const projectTypeOverride = getArgValue(args, "--project-type");
|
|
166
|
-
const silent = reportJsonOnly;
|
|
167
|
-
|
|
168
|
-
if (reportJsonOnly && reportHumanOnly) {
|
|
169
|
-
console.error("Error: --report-json-only and --report-human-only cannot be used together.");
|
|
170
|
-
process.exit(1);
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
if (!silent) {
|
|
174
|
-
header("init");
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
const infernoDir = path.join(cwd, "inferno");
|
|
178
|
-
const workflowsDir = path.join(cwd, ".github", "workflows");
|
|
179
|
-
if (fs.existsSync(infernoDir) && !force) {
|
|
180
|
-
if (silent) {
|
|
181
|
-
console.log(JSON.stringify({ ok: false, error: "inferno_exists", hint: "Use --force to overwrite" }, null, 2));
|
|
182
|
-
process.exit(1);
|
|
183
|
-
}
|
|
184
|
-
warn("inferno/ already exists. Use --force to overwrite.");
|
|
185
|
-
console.log();
|
|
186
|
-
process.exit(0);
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
const detectedName = detectProjectName(cwd);
|
|
190
|
-
const defaultCaps = "CreateTask, ReadTasks, UpdateTask, ToggleComplete, DeleteTask";
|
|
191
|
-
|
|
192
|
-
let policyId = detectedName;
|
|
193
|
-
let capabilities = defaultCaps.split(",").map(c => c.trim());
|
|
194
|
-
|
|
195
|
-
if (adopt) {
|
|
196
|
-
const profileOverrides = {
|
|
197
|
-
language: langOverride || undefined,
|
|
198
|
-
framework: frameworkOverride || undefined,
|
|
199
|
-
projectType: projectTypeOverride || undefined,
|
|
200
|
-
};
|
|
201
|
-
let signals = discoverProjectSignals(cwd, profileOverrides);
|
|
202
|
-
if (!yes && !reportJsonOnly) {
|
|
203
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
204
|
-
const profile = signals.developmentProfile || {};
|
|
205
|
-
const detected = profile.detected || {};
|
|
206
|
-
console.log(gray(" Review inferred development stack (press Enter to accept detected values)\n"));
|
|
207
|
-
const language = await ask(rl, "Language", profile.language || detected.language || "unknown");
|
|
208
|
-
const framework = await ask(rl, "Framework", profile.framework || detected.framework || "unknown");
|
|
209
|
-
const projectType = await ask(rl, "Project type", profile.projectType || detected.projectType || "unknown");
|
|
210
|
-
rl.close();
|
|
211
|
-
signals = discoverProjectSignals(cwd, { language, framework, projectType });
|
|
212
|
-
}
|
|
213
|
-
const inferred = signals.capabilities;
|
|
214
|
-
const summarized = summarizeCapabilities(inferred);
|
|
215
|
-
if (reportJsonOnly) {
|
|
216
|
-
console.log(
|
|
217
|
-
JSON.stringify(
|
|
218
|
-
{
|
|
219
|
-
mode: "adopt",
|
|
220
|
-
policyId: detectedName,
|
|
221
|
-
inferredCapabilities: summarized,
|
|
222
|
-
components: signals.components,
|
|
223
|
-
displayFields: signals.displayFields,
|
|
224
|
-
externalLibraries: signals.externalLibraries,
|
|
225
|
-
uiLayout: signals.uiLayout,
|
|
226
|
-
styling: signals.styling,
|
|
227
|
-
developmentProfile: signals.developmentProfile,
|
|
228
|
-
apiCalls: signals.apiCalls,
|
|
229
|
-
},
|
|
230
|
-
null,
|
|
231
|
-
2
|
|
232
|
-
)
|
|
233
|
-
);
|
|
234
|
-
} else {
|
|
235
|
-
console.log();
|
|
236
|
-
console.log(gray(buildAdoptionReport(inferred)));
|
|
237
|
-
console.log();
|
|
238
|
-
console.log(gray(buildSignalsReport(signals)));
|
|
239
|
-
console.log();
|
|
240
|
-
if (reportJson && !reportHumanOnly) {
|
|
241
|
-
console.log(
|
|
242
|
-
JSON.stringify(
|
|
243
|
-
{
|
|
244
|
-
mode: "adopt",
|
|
245
|
-
policyId: detectedName,
|
|
246
|
-
inferredCapabilities: summarized,
|
|
247
|
-
components: signals.components,
|
|
248
|
-
displayFields: signals.displayFields,
|
|
249
|
-
externalLibraries: signals.externalLibraries,
|
|
250
|
-
uiLayout: signals.uiLayout,
|
|
251
|
-
styling: signals.styling,
|
|
252
|
-
developmentProfile: signals.developmentProfile,
|
|
253
|
-
apiCalls: signals.apiCalls,
|
|
254
|
-
},
|
|
255
|
-
null,
|
|
256
|
-
2
|
|
257
|
-
)
|
|
258
|
-
);
|
|
259
|
-
console.log();
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
const reviewed = await reviewCapabilitiesInteractive(inferred, yes);
|
|
263
|
-
policyId = detectedName;
|
|
264
|
-
capabilities = reviewed.map((c) => c.id);
|
|
265
|
-
} else if (!yes) {
|
|
266
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
267
|
-
console.log(gray(" Press Enter to accept defaults\n"));
|
|
268
|
-
policyId = await ask(rl, "Project / policy name", detectedName);
|
|
269
|
-
const capsRaw = await ask(rl, "Capabilities (comma-separated)", defaultCaps);
|
|
270
|
-
capabilities = capsRaw.split(",").map(c => c.trim()).filter(Boolean);
|
|
271
|
-
rl.close();
|
|
272
|
-
console.log();
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
// Write files
|
|
276
|
-
fs.mkdirSync(infernoDir, { recursive: true });
|
|
277
|
-
|
|
278
|
-
if (adopt) {
|
|
279
|
-
const capDetails = capabilities.map((id) => ({
|
|
280
|
-
id,
|
|
281
|
-
title: id.replace(/([A-Z])/g, " $1").trim(),
|
|
282
|
-
}));
|
|
283
|
-
const signals = discoverProjectSignals(cwd, {
|
|
284
|
-
language: langOverride || undefined,
|
|
285
|
-
framework: frameworkOverride || undefined,
|
|
286
|
-
projectType: projectTypeOverride || undefined,
|
|
287
|
-
});
|
|
288
|
-
writeAdoptionBaseline(infernoDir, policyId, capDetails, signals);
|
|
289
|
-
if (!silent) {
|
|
290
|
-
ok("Created: " + cyan("inferno/contract.json"));
|
|
291
|
-
ok("Created: " + cyan("inferno/capabilities.json"));
|
|
292
|
-
ok("Created: " + cyan("inferno/scenarios/adoption_baseline.json"));
|
|
293
|
-
ok("Created: " + cyan("inferno/adoption_profile.json"));
|
|
294
|
-
ok("Created: " + cyan("inferno/CHANGELOG.md"));
|
|
295
|
-
}
|
|
296
|
-
} else {
|
|
297
|
-
writeContract(path.join(infernoDir, "contract.json"), policyId, capabilities);
|
|
298
|
-
if (!silent) ok("Created: " + cyan("inferno/contract.json"));
|
|
299
|
-
|
|
300
|
-
writeCapabilities(path.join(infernoDir, "capabilities.json"), capabilities);
|
|
301
|
-
if (!silent) ok("Created: " + cyan("inferno/capabilities.json"));
|
|
302
|
-
|
|
303
|
-
writeScenario(path.join(infernoDir, "scenarios"), capabilities);
|
|
304
|
-
if (!silent) ok("Created: " + cyan("inferno/scenarios/happy_path.json"));
|
|
305
|
-
|
|
306
|
-
writeChangelog(path.join(infernoDir, "CHANGELOG.md"), policyId);
|
|
307
|
-
if (!silent) ok("Created: " + cyan("inferno/CHANGELOG.md"));
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
// Copy doc-gate script
|
|
311
|
-
const templates = getTemplatesRoot();
|
|
312
|
-
const srcScript = path.join(templates, "scripts", "inferno-doc-gate.mjs");
|
|
313
|
-
const dstScript = path.join(cwd, "scripts", "inferno-doc-gate.mjs");
|
|
314
|
-
copyFile(srcScript, dstScript, force, silent);
|
|
315
|
-
const srcHookScript = path.join(templates, "scripts", "inferno-install-hooks.mjs");
|
|
316
|
-
const dstHookScript = path.join(cwd, "scripts", "inferno-install-hooks.mjs");
|
|
317
|
-
copyFile(srcHookScript, dstHookScript, force, silent);
|
|
318
|
-
const srcWorkflow = path.join(templates, "ci", "github-inferno-check.yml");
|
|
319
|
-
const dstWorkflow = path.join(workflowsDir, "infernoflow-check.yml");
|
|
320
|
-
copyFile(srcWorkflow, dstWorkflow, force, silent);
|
|
321
|
-
|
|
322
|
-
upsertScripts(cwd, silent);
|
|
323
|
-
|
|
324
|
-
if (cursorHooks) {
|
|
325
|
-
installCursorHooksArtifacts({
|
|
326
|
-
cwd,
|
|
327
|
-
templatesRoot: templates,
|
|
328
|
-
force,
|
|
329
|
-
silent,
|
|
330
|
-
logOk: (msg) => {
|
|
331
|
-
if (!silent) ok(msg);
|
|
332
|
-
},
|
|
333
|
-
logWarn: (msg) => {
|
|
334
|
-
if (!silent) warn(msg);
|
|
335
|
-
},
|
|
336
|
-
});
|
|
337
|
-
}
|
|
338
|
-
if (vscodeCopilotHooks) {
|
|
339
|
-
installVsCodeCopilotHooksArtifacts({
|
|
340
|
-
cwd,
|
|
341
|
-
templatesRoot: templates,
|
|
342
|
-
force,
|
|
343
|
-
silent,
|
|
344
|
-
logOk: (msg) => {
|
|
345
|
-
if (!silent) ok(msg);
|
|
346
|
-
},
|
|
347
|
-
logWarn: (msg) => {
|
|
348
|
-
if (!silent) warn(msg);
|
|
349
|
-
},
|
|
350
|
-
});
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
if (adopt) {
|
|
354
|
-
const statePath = path.join(infernoDir, "context-state.json");
|
|
355
|
-
let state = {};
|
|
356
|
-
try {
|
|
357
|
-
state = JSON.parse(fs.readFileSync(statePath, "utf8"));
|
|
358
|
-
} catch {}
|
|
359
|
-
const signals = discoverProjectSignals(cwd, {
|
|
360
|
-
language: langOverride || undefined,
|
|
361
|
-
framework: frameworkOverride || undefined,
|
|
362
|
-
projectType: projectTypeOverride || undefined,
|
|
363
|
-
});
|
|
364
|
-
state.stack = signals.developmentProfile;
|
|
365
|
-
fs.writeFileSync(statePath, JSON.stringify(state, null, 2) + "\n", "utf8");
|
|
366
|
-
if (!silent) ok("Created: " + cyan("inferno/context-state.json"));
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
if (!silent) {
|
|
370
|
-
done("infernoflow initialized!");
|
|
371
|
-
|
|
372
|
-
nextSteps([
|
|
373
|
-
cyan("infernoflow status") + " — see your contract at a glance",
|
|
374
|
-
cyan("infernoflow check") + " — validate everything",
|
|
375
|
-
(adopt ? "Review inferred baseline in " : "Edit ") + yellow("inferno/capabilities.json") + (adopt ? " and refine IDs/titles" : " to describe each capability in detail"),
|
|
376
|
-
"Add more " + yellow("inferno/scenarios/*.json") + " files for edge cases",
|
|
377
|
-
"Add " + cyan("inferno:check") + " to your CI pipeline",
|
|
378
|
-
...(cursorHooks
|
|
379
|
-
? [
|
|
380
|
-
"Restart Cursor — hooks write assistant text to " + yellow("inferno/CONTEXT.draft.md"),
|
|
381
|
-
"Promote when ready: " + cyan("npm run inferno:promote-draft -- --append-notes"),
|
|
382
|
-
]
|
|
383
|
-
: []),
|
|
384
|
-
...(vscodeCopilotHooks
|
|
385
|
-
? [
|
|
386
|
-
"Restart VS Code — Copilot hooks append prompts + assistant (from transcript) to " +
|
|
387
|
-
yellow("inferno/CONTEXT.draft.md"),
|
|
388
|
-
"Promote when ready: " + cyan("npm run inferno:promote-draft -- --append-notes"),
|
|
389
|
-
]
|
|
390
|
-
: []),
|
|
391
|
-
...(!cursorHooks && !vscodeCopilotHooks
|
|
392
|
-
? [
|
|
393
|
-
"Optional: " +
|
|
394
|
-
cyan("infernoflow install-cursor-hooks") +
|
|
395
|
-
" or " +
|
|
396
|
-
cyan("infernoflow install-vscode-copilot-hooks"),
|
|
397
|
-
]
|
|
398
|
-
: []),
|
|
399
|
-
]);
|
|
400
|
-
}
|
|
401
|
-
}
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import * as path from "node:path";
|
|
2
|
-
import { fileURLToPath } from "node:url";
|
|
3
|
-
import { header, ok, warn, done, nextSteps, cyan, yellow } from "../ui/output.mjs";
|
|
4
|
-
import { installCursorHooksArtifacts } from "../cursorHooksInstall.mjs";
|
|
5
|
-
|
|
6
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
7
|
-
|
|
8
|
-
function getTemplatesRoot() {
|
|
9
|
-
return path.resolve(__dirname, "../../templates");
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export async function installCursorHooksCommand(args) {
|
|
13
|
-
const cwd = process.cwd();
|
|
14
|
-
const force = args.includes("--force") || args.includes("-f");
|
|
15
|
-
|
|
16
|
-
header("install-cursor-hooks");
|
|
17
|
-
|
|
18
|
-
installCursorHooksArtifacts({
|
|
19
|
-
cwd,
|
|
20
|
-
templatesRoot: getTemplatesRoot(),
|
|
21
|
-
force,
|
|
22
|
-
silent: false,
|
|
23
|
-
logOk: (msg) => ok(msg),
|
|
24
|
-
logWarn: (msg) => warn(msg),
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
done("Cursor draft hooks installed");
|
|
28
|
-
|
|
29
|
-
nextSteps([
|
|
30
|
-
"Restart Cursor (or reload window) so " + yellow(".cursor/hooks.json") + " is picked up",
|
|
31
|
-
"Use Agent chat — each assistant reply appends to " + yellow("inferno/CONTEXT.draft.md") + " (gitignored)",
|
|
32
|
-
cyan("npm run inferno:promote-draft") + " — preview draft",
|
|
33
|
-
cyan("npm run inferno:promote-draft -- --append-notes") + " — merge into inferno/CONTEXT.md under Decisions",
|
|
34
|
-
cyan("npm run inferno:promote-draft -- --clear") + " — discard draft",
|
|
35
|
-
]);
|
|
36
|
-
}
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import * as path from "node:path";
|
|
2
|
-
import { fileURLToPath } from "node:url";
|
|
3
|
-
import { header, ok, warn, done, nextSteps, cyan, yellow } from "../ui/output.mjs";
|
|
4
|
-
import { installVsCodeCopilotHooksArtifacts } from "../vsCodeCopilotHooksInstall.mjs";
|
|
5
|
-
|
|
6
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
7
|
-
|
|
8
|
-
function getTemplatesRoot() {
|
|
9
|
-
return path.resolve(__dirname, "../../templates");
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export async function installVsCodeCopilotHooksCommand(args) {
|
|
13
|
-
const cwd = process.cwd();
|
|
14
|
-
const force = args.includes("--force") || args.includes("-f");
|
|
15
|
-
|
|
16
|
-
header("install-vscode-copilot-hooks");
|
|
17
|
-
|
|
18
|
-
installVsCodeCopilotHooksArtifacts({
|
|
19
|
-
cwd,
|
|
20
|
-
templatesRoot: getTemplatesRoot(),
|
|
21
|
-
force,
|
|
22
|
-
silent: false,
|
|
23
|
-
logOk: (msg) => ok(msg),
|
|
24
|
-
logWarn: (msg) => warn(msg),
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
done("VS Code / Copilot draft hooks installed");
|
|
28
|
-
|
|
29
|
-
nextSteps([
|
|
30
|
-
"Requires VS Code + GitHub Copilot and **Agent hooks (Preview)** — see " +
|
|
31
|
-
yellow("https://code.visualstudio.com/docs/copilot/customization/hooks"),
|
|
32
|
-
"Hooks load from " + yellow(".github/hooks/*.json") + " — restart VS Code or reload window after first install",
|
|
33
|
-
"Check the **GitHub Copilot Chat Hooks** output channel if nothing runs",
|
|
34
|
-
cyan("npm run inferno:promote-draft") + " — preview draft",
|
|
35
|
-
cyan("npm run inferno:promote-draft -- --append-notes") + " — merge into inferno/CONTEXT.md",
|
|
36
|
-
]);
|
|
37
|
-
}
|
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
import { execSync } from "node:child_process";
|
|
2
|
-
import * as fs from "node:fs";
|
|
3
|
-
import * as path from "node:path";
|
|
4
|
-
import { header, section, ok, warn, fail, gray, cyan, yellow } from "../ui/output.mjs";
|
|
5
|
-
|
|
6
|
-
const CODE_PREFIXES = ["src/", "frontend/", "backend/", "app/", "pages/", "components/", "lib/", "api/", "server/", "Controllers/"];
|
|
7
|
-
|
|
8
|
-
function sh(cmd) {
|
|
9
|
-
return execSync(cmd, { stdio: ["ignore", "pipe", "pipe"] }).toString("utf8").trim();
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
function readJson(filePath, fallback = null) {
|
|
13
|
-
try {
|
|
14
|
-
return JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
15
|
-
} catch {
|
|
16
|
-
return fallback;
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function readFile(filePath, fallback = "") {
|
|
21
|
-
try {
|
|
22
|
-
return fs.readFileSync(filePath, "utf8");
|
|
23
|
-
} catch {
|
|
24
|
-
return fallback;
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function getChangedFiles(base, head) {
|
|
29
|
-
const out = base && head
|
|
30
|
-
? sh(`git diff --name-only ${base}..${head}`)
|
|
31
|
-
: sh("git diff --name-only HEAD");
|
|
32
|
-
return out ? out.split("\n").map((s) => s.trim()).filter(Boolean) : [];
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function buildCapabilityHints(cwd) {
|
|
36
|
-
const infernoDir = path.join(cwd, "inferno");
|
|
37
|
-
const contract = readJson(path.join(infernoDir, "contract.json"), { capabilities: [] });
|
|
38
|
-
const registry = readJson(path.join(infernoDir, "capabilities.json"), { capabilities: [] });
|
|
39
|
-
const titleById = new Map((registry.capabilities || []).map((c) => [c.id, c.title || c.id]));
|
|
40
|
-
return (contract.capabilities || []).map((id) => {
|
|
41
|
-
const title = titleById.get(id) || id;
|
|
42
|
-
const keywords = new Set(
|
|
43
|
-
`${id} ${title}`
|
|
44
|
-
.replace(/([A-Z])/g, " $1")
|
|
45
|
-
.toLowerCase()
|
|
46
|
-
.split(/[^a-z0-9]+/)
|
|
47
|
-
.filter((k) => k.length >= 4)
|
|
48
|
-
);
|
|
49
|
-
return { id, title, keywords: Array.from(keywords) };
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function inferImpactedCapabilities(cwd, changedCodeFiles) {
|
|
54
|
-
const hints = buildCapabilityHints(cwd);
|
|
55
|
-
const impacted = [];
|
|
56
|
-
for (const hint of hints) {
|
|
57
|
-
const matched = [];
|
|
58
|
-
for (const rel of changedCodeFiles) {
|
|
59
|
-
const abs = path.join(cwd, rel);
|
|
60
|
-
const text = readFile(abs, "").toLowerCase();
|
|
61
|
-
if (!text) continue;
|
|
62
|
-
if (hint.keywords.some((k) => text.includes(k))) {
|
|
63
|
-
matched.push(rel);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
if (matched.length) {
|
|
67
|
-
impacted.push({ id: hint.id, title: hint.title, matchedFiles: matched.slice(0, 5) });
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
return impacted;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
export async function prImpactCommand(args = []) {
|
|
74
|
-
const asJson = args.includes("--json");
|
|
75
|
-
const cwd = process.cwd();
|
|
76
|
-
const base = process.env.BASE_SHA || null;
|
|
77
|
-
const head = process.env.HEAD_SHA || null;
|
|
78
|
-
|
|
79
|
-
let changedFiles = [];
|
|
80
|
-
try {
|
|
81
|
-
changedFiles = getChangedFiles(base, head);
|
|
82
|
-
} catch {
|
|
83
|
-
const payload = { ok: true, skipped: true, reason: "no_git_available" };
|
|
84
|
-
if (asJson) {
|
|
85
|
-
console.log(JSON.stringify(payload, null, 2));
|
|
86
|
-
return;
|
|
87
|
-
}
|
|
88
|
-
header("pr-impact");
|
|
89
|
-
warn("git not available; cannot compute PR impact");
|
|
90
|
-
console.log();
|
|
91
|
-
return;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
const changedCodeFiles = changedFiles.filter((f) => CODE_PREFIXES.some((p) => f.startsWith(p)));
|
|
95
|
-
const changedInfernoFiles = changedFiles.filter((f) => f.startsWith("inferno/"));
|
|
96
|
-
const impactedCapabilities = inferImpactedCapabilities(cwd, changedCodeFiles);
|
|
97
|
-
const inferredBehaviorChange = changedCodeFiles.length > 0;
|
|
98
|
-
const missingInfernoUpdate = inferredBehaviorChange && changedInfernoFiles.length === 0;
|
|
99
|
-
const confidence = impactedCapabilities.length > 0 ? "high" : inferredBehaviorChange ? "medium" : "low";
|
|
100
|
-
const reasonCodes = [];
|
|
101
|
-
if (inferredBehaviorChange) reasonCodes.push("CODE_CHANGED");
|
|
102
|
-
if (missingInfernoUpdate) reasonCodes.push("INFERNO_NOT_UPDATED");
|
|
103
|
-
if (impactedCapabilities.length > 0) reasonCodes.push("CAPABILITY_HINT_MATCH");
|
|
104
|
-
if (!reasonCodes.length) reasonCodes.push("NO_BEHAVIOR_SIGNAL");
|
|
105
|
-
|
|
106
|
-
const payload = {
|
|
107
|
-
ok: !missingInfernoUpdate,
|
|
108
|
-
base: base || "HEAD",
|
|
109
|
-
head: head || "WORKTREE",
|
|
110
|
-
changedFiles,
|
|
111
|
-
changedCodeFiles,
|
|
112
|
-
changedInfernoFiles,
|
|
113
|
-
inferredBehaviorChange,
|
|
114
|
-
impactedCapabilities,
|
|
115
|
-
confidence,
|
|
116
|
-
reasonCodes,
|
|
117
|
-
recommendations: missingInfernoUpdate
|
|
118
|
-
? ["Run infernoflow suggest \"describe behavior change\" and update inferno/", "Run infernoflow check --json"]
|
|
119
|
-
: ["Run infernoflow check --json to validate final state"],
|
|
120
|
-
};
|
|
121
|
-
|
|
122
|
-
if (asJson) {
|
|
123
|
-
console.log(JSON.stringify(payload, null, 2));
|
|
124
|
-
process.exit(payload.ok ? 0 : 1);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
header("pr-impact");
|
|
128
|
-
|
|
129
|
-
section("Diff Scope");
|
|
130
|
-
ok(`Changed files: ${cyan(String(changedFiles.length))}`);
|
|
131
|
-
ok(`Code files: ${cyan(String(changedCodeFiles.length))}`);
|
|
132
|
-
ok(`Inferno files: ${cyan(String(changedInfernoFiles.length))}`);
|
|
133
|
-
|
|
134
|
-
section("Capability Impact");
|
|
135
|
-
if (impactedCapabilities.length === 0) {
|
|
136
|
-
warn("No capability hints matched changed code files");
|
|
137
|
-
} else {
|
|
138
|
-
impactedCapabilities.forEach((c) => {
|
|
139
|
-
console.log(` ${cyan("•")} ${c.id} ${gray(`(${c.title})`)}`);
|
|
140
|
-
c.matchedFiles.slice(0, 3).forEach((f) => console.log(` ${gray("- " + f)}`));
|
|
141
|
-
});
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
section("Doc Sync");
|
|
145
|
-
if (missingInfernoUpdate) {
|
|
146
|
-
fail("Code changed but inferno/ was not updated", "Run infernoflow suggest and then infernoflow check");
|
|
147
|
-
} else {
|
|
148
|
-
ok("No immediate inferno drift signal from changed files");
|
|
149
|
-
}
|
|
150
|
-
ok(`Confidence: ${cyan(confidence)}`);
|
|
151
|
-
|
|
152
|
-
section("Suggested Next");
|
|
153
|
-
payload.recommendations.forEach((r) => console.log(` ${yellow("→")} ${r}`));
|
|
154
|
-
console.log();
|
|
155
|
-
process.exit(payload.ok ? 0 : 1);
|
|
156
|
-
}
|
|
157
|
-
|