@ijfw/install 1.3.1 → 1.3.2
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 +19 -1
- package/dist/ijfw.js +315 -69
- package/dist/install.js +18 -2
- package/dist/uninstall.js +40 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.3.2] -- 2026-05-15
|
|
4
|
+
|
|
5
|
+
**Project-agnostic swarm orchestration + live visual workflow + richer Codex native surface.** Adds first-class Team Assembly, blackboard coordination, swarm task lifecycle, conservative git worktrees, recovery checkpoints, Superpowers-style live design previews, and Claude-parity Codex command aliases.
|
|
6
|
+
|
|
7
|
+
- `ijfw design start/open/status/stop/push/clear` now runs a localhost visual companion with live reload, placeholder preview, and multi-file HTML support under `/design`.
|
|
8
|
+
- `ijfw team init/status` creates project-specific agent charters and workflow manifests across software, design, content, book, research, business, and mixed projects.
|
|
9
|
+
- `.ijfw/blackboard/` now provides runtime coordination: artifact claims, notes/findings/decisions/blockers, handoffs, prepared tasks, and append-only events.
|
|
10
|
+
- `ijfw swarm plan/prepare/tasks/start/complete/block/ready/status` turns team workflows into artifact-aware parallel waves with explicit dependencies and review tasks.
|
|
11
|
+
- `ijfw swarm prompt <task-id>` renders a scoped worker prompt for subagents, including artifact scope, dependencies, blockers, checks, blackboard commands, and non-revert rules.
|
|
12
|
+
- Codex-native dispatch now covers `ijfw codex doctor`, `ijfw codex sync-agents`, `.codex/agents/*.toml` generation from Team Assembly, and `ijfw swarm prompt <task-id> --codex` for pasteable Codex worker/explorer dispatch.
|
|
13
|
+
- `ijfw swarm worktree create/list/integrate/cleanup` adds conservative isolated git worktrees for in-progress code-heavy tasks, with no automatic conflict resolution.
|
|
14
|
+
- `ijfw memory checkpoint <label>` and `ijfw recover status/latest` create durable workflow snapshots so project state can be recovered after context loss.
|
|
15
|
+
- Workflow and team skills now include SWARM/PREPARE lifecycle guidance, review gates, worktree safety rules, and checkpoint expectations at stage transitions.
|
|
16
|
+
- Codex now ships the same 22 IJFW command aliases as Claude, installs them to `~/.codex/commands` and project `.codex/commands` when applicable, and advertises `commands_dir` in the Codex plugin manifest.
|
|
17
|
+
- The terminal CLI exposes the same command vocabulary as the agent surfaces: `ijfw cross audit <target>`, `ijfw cross-audit`, `ijfw workflow`, `ijfw memory-audit`, `ijfw ijfw-verify`, and related aliases all route through the shared IJFW command dispatcher.
|
|
18
|
+
|
|
19
|
+
Files: `mcp-server/src/blackboard.js`, `mcp-server/src/team/`, `mcp-server/src/swarm/`, `mcp-server/src/recovery/`, `mcp-server/src/dashboard-server.js`, `mcp-server/src/cross-orchestrator-cli.js`, `installer/src/ijfw.js`, `shared/skills/ijfw-workflow/SKILL.md`, platform workflow/team/design skill mirrors, and related fixtures/tests.
|
|
20
|
+
|
|
3
21
|
## [1.3.1] -- 2026-05-12
|
|
4
22
|
|
|
5
23
|
**Codex hook cleanup + release cadence hardening.** Tightens IJFW's Codex integration for Codex 0.130+, reduces the default dependency footprint, and moves more release drift into automated gates before it reaches users.
|
|
@@ -659,7 +677,7 @@ Platform count: **8 install targets -> 13 MCP-integrated + 1 rules-only**. Same
|
|
|
659
677
|
|
|
660
678
|
- `ijfw preflight` -- 11-gate quality pipeline covering shell lint, JS lint, security scan, secret detection, npm audit, dead-code detection, license check, pack-smoke, and upgrade-smoke.
|
|
661
679
|
- Blocking vs advisory distinction: exit 0 when all blocking gates pass even if advisory warnings exist. Exit 1 on any blocking failure.
|
|
662
|
-
-
|
|
680
|
+
- Tool-backed gates use `npx --yes <tool>@<pinned-version>`. The dependency-audit gate uses `npm audit --json` directly against the package lockfiles. Pinned versions live in `preflight-versions.json`; missing tools report "skipped" with a positive install hint, not a failure.
|
|
663
681
|
- Warm-cache SLO: <=90s. Cold-cache: <=240s. Both printed in the summary line.
|
|
664
682
|
- `prepublishOnly` in `installer/package.json` now runs preflight before every publish so no tag can ship with a blocking gate open.
|
|
665
683
|
|
package/dist/ijfw.js
CHANGED
|
@@ -267,6 +267,17 @@ import { spawnSync as spawnSync3 } from "node:child_process";
|
|
|
267
267
|
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
268
268
|
import { join as join2 } from "node:path";
|
|
269
269
|
import { tmpdir } from "node:os";
|
|
270
|
+
function formatMessages(results, severity12) {
|
|
271
|
+
const out = [];
|
|
272
|
+
for (const file of results) {
|
|
273
|
+
for (const msg of file.messages || []) {
|
|
274
|
+
if (severity12 === "error" && msg.severity !== 2) continue;
|
|
275
|
+
if (severity12 === "warning" && msg.severity !== 1) continue;
|
|
276
|
+
out.push(`${file.filePath}:${msg.line}:${msg.column} ${msg.severity === 2 ? "error" : "warning"} ${msg.ruleId} -- ${msg.message}`);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
return out;
|
|
280
|
+
}
|
|
270
281
|
async function run3(ctx) {
|
|
271
282
|
const t0 = Date.now();
|
|
272
283
|
const eslintVer = ctx.versions["eslint"] || "latest";
|
|
@@ -292,6 +303,7 @@ async function run3(ctx) {
|
|
|
292
303
|
import security from '${join2(tmpDir, "node_modules", "eslint-plugin-security", "index.js").replace(/\\/g, "/")}';
|
|
293
304
|
export default [
|
|
294
305
|
{
|
|
306
|
+
linterOptions: { reportUnusedDisableDirectives: 'off' },
|
|
295
307
|
files: ['installer/src/**/*.js', 'mcp-server/src/**/*.js'],
|
|
296
308
|
plugins: { security },
|
|
297
309
|
rules: ${JSON.stringify(RULES)},
|
|
@@ -303,13 +315,20 @@ export default [
|
|
|
303
315
|
const eslintBin = join2(tmpDir, "node_modules", ".bin", "eslint");
|
|
304
316
|
const res = spawnSync3(
|
|
305
317
|
eslintBin,
|
|
306
|
-
["--config", configPath, "installer/src/**/*.js", "mcp-server/src/**/*.js"],
|
|
318
|
+
["--no-config-lookup", "--config", configPath, "--format", "json", "installer/src/**/*.js", "mcp-server/src/**/*.js"],
|
|
307
319
|
{ encoding: "utf8", cwd: ctx.repoRoot, timeout: 6e4 }
|
|
308
320
|
);
|
|
309
321
|
const durationMs = Date.now() - t0;
|
|
310
322
|
const output = (res.stdout || "") + (res.stderr || "");
|
|
311
|
-
|
|
312
|
-
|
|
323
|
+
let results = [];
|
|
324
|
+
try {
|
|
325
|
+
results = JSON.parse(res.stdout || "[]");
|
|
326
|
+
} catch {
|
|
327
|
+
results = [];
|
|
328
|
+
}
|
|
329
|
+
const errorCount = results.reduce((sum, file) => sum + (file.errorCount || 0), 0);
|
|
330
|
+
const warningCount = results.reduce((sum, file) => sum + (file.warningCount || 0), 0);
|
|
331
|
+
if (res.status === 0 && errorCount === 0 && warningCount === 0) {
|
|
313
332
|
return {
|
|
314
333
|
name: "eslint-security",
|
|
315
334
|
status: "PASS",
|
|
@@ -318,20 +337,20 @@ export default [
|
|
|
318
337
|
durationMs
|
|
319
338
|
};
|
|
320
339
|
}
|
|
321
|
-
if (res.status ===
|
|
340
|
+
if (errorCount > 0 || res.status === 2) {
|
|
322
341
|
return {
|
|
323
342
|
name: "eslint-security",
|
|
324
|
-
status: "
|
|
325
|
-
message:
|
|
326
|
-
details:
|
|
343
|
+
status: "FAIL",
|
|
344
|
+
message: `eslint-security: ${errorCount || "unknown"} security error(s) found`,
|
|
345
|
+
details: (formatMessages(results, "error").length ? formatMessages(results, "error") : output.split("\n").filter(Boolean)).slice(0, 30),
|
|
327
346
|
durationMs
|
|
328
347
|
};
|
|
329
348
|
}
|
|
330
349
|
return {
|
|
331
350
|
name: "eslint-security",
|
|
332
|
-
status: "
|
|
333
|
-
message:
|
|
334
|
-
details:
|
|
351
|
+
status: "WARN",
|
|
352
|
+
message: `eslint-security: ${warningCount} advisory warning(s)`,
|
|
353
|
+
details: (formatMessages(results, "warning").length ? formatMessages(results, "warning") : output.split("\n").filter(Boolean)).slice(0, 30),
|
|
335
354
|
durationMs
|
|
336
355
|
};
|
|
337
356
|
} finally {
|
|
@@ -350,11 +369,7 @@ var init_eslint_security = __esm({
|
|
|
350
369
|
"src/preflight/gates/eslint-security.js"() {
|
|
351
370
|
RULES = {
|
|
352
371
|
"security/detect-eval-with-expression": "error",
|
|
353
|
-
"security/detect-non-literal-fs-filename": "warn",
|
|
354
|
-
"security/detect-non-literal-regexp": "warn",
|
|
355
372
|
"security/detect-non-literal-require": "warn",
|
|
356
|
-
"security/detect-object-injection": "warn",
|
|
357
|
-
"security/detect-possible-timing-attacks": "warn",
|
|
358
373
|
"security/detect-pseudoRandomBytes": "error",
|
|
359
374
|
"security/detect-unsafe-regex": "error"
|
|
360
375
|
};
|
|
@@ -367,15 +382,15 @@ var init_eslint_security = __esm({
|
|
|
367
382
|
// src/preflight/gates/psscriptanalyzer.js
|
|
368
383
|
var psscriptanalyzer_exports = {};
|
|
369
384
|
__export(psscriptanalyzer_exports, {
|
|
385
|
+
fallbackAnalyzePowerShellText: () => fallbackAnalyzePowerShellText,
|
|
370
386
|
name: () => name4,
|
|
371
387
|
parallel: () => parallel4,
|
|
372
388
|
run: () => run4,
|
|
373
389
|
severity: () => severity4
|
|
374
390
|
});
|
|
375
391
|
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
376
|
-
import { readdirSync as readdirSync2, statSync as statSync2 } from "node:fs";
|
|
392
|
+
import { readFileSync, readdirSync as readdirSync2, statSync as statSync2 } from "node:fs";
|
|
377
393
|
import { join as join3 } from "node:path";
|
|
378
|
-
import { platform } from "node:os";
|
|
379
394
|
function findPs1Files(dir, acc = []) {
|
|
380
395
|
let entries;
|
|
381
396
|
try {
|
|
@@ -397,6 +412,109 @@ function findPs1Files(dir, acc = []) {
|
|
|
397
412
|
}
|
|
398
413
|
return acc;
|
|
399
414
|
}
|
|
415
|
+
function stripPowerShellComments(source) {
|
|
416
|
+
let out = "";
|
|
417
|
+
let i = 0;
|
|
418
|
+
let inSingle = false;
|
|
419
|
+
let inDouble = false;
|
|
420
|
+
let inBlockComment = false;
|
|
421
|
+
while (i < source.length) {
|
|
422
|
+
const ch = source[i];
|
|
423
|
+
const next = source[i + 1];
|
|
424
|
+
if (inBlockComment) {
|
|
425
|
+
if (ch === "#" && next === ">") {
|
|
426
|
+
inBlockComment = false;
|
|
427
|
+
i += 2;
|
|
428
|
+
} else {
|
|
429
|
+
if (ch === "\n") out += "\n";
|
|
430
|
+
i += 1;
|
|
431
|
+
}
|
|
432
|
+
continue;
|
|
433
|
+
}
|
|
434
|
+
if (!inSingle && !inDouble && ch === "<" && next === "#") {
|
|
435
|
+
inBlockComment = true;
|
|
436
|
+
i += 2;
|
|
437
|
+
continue;
|
|
438
|
+
}
|
|
439
|
+
if (!inSingle && !inDouble && ch === "#") {
|
|
440
|
+
while (i < source.length && source[i] !== "\n") i += 1;
|
|
441
|
+
out += "\n";
|
|
442
|
+
continue;
|
|
443
|
+
}
|
|
444
|
+
out += ch;
|
|
445
|
+
if (!inDouble && ch === "'") inSingle = !inSingle;
|
|
446
|
+
else if (!inSingle && ch === '"' && source[i - 1] !== "`") inDouble = !inDouble;
|
|
447
|
+
i += 1;
|
|
448
|
+
}
|
|
449
|
+
return out;
|
|
450
|
+
}
|
|
451
|
+
function bracketIssues(source, file) {
|
|
452
|
+
const pairs = { "(": ")", "[": "]", "{": "}" };
|
|
453
|
+
const closers = new Set(Object.values(pairs));
|
|
454
|
+
const stack = [];
|
|
455
|
+
let inSingle = false;
|
|
456
|
+
let inDouble = false;
|
|
457
|
+
for (let i = 0; i < source.length; i++) {
|
|
458
|
+
const ch = source[i];
|
|
459
|
+
if (!inDouble && ch === "'") {
|
|
460
|
+
inSingle = !inSingle;
|
|
461
|
+
continue;
|
|
462
|
+
}
|
|
463
|
+
if (!inSingle && ch === '"' && source[i - 1] !== "`") {
|
|
464
|
+
inDouble = !inDouble;
|
|
465
|
+
continue;
|
|
466
|
+
}
|
|
467
|
+
if (inSingle || inDouble) continue;
|
|
468
|
+
if (pairs[ch]) stack.push({ ch, index: i });
|
|
469
|
+
else if (closers.has(ch)) {
|
|
470
|
+
const open = stack.pop();
|
|
471
|
+
if (!open || pairs[open.ch] !== ch) return [`${file}: unbalanced bracket near offset ${i}`];
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
if (inSingle || inDouble) return [`${file}: unterminated string literal`];
|
|
475
|
+
if (stack.length > 0) return [`${file}: unclosed bracket near offset ${stack[stack.length - 1].index}`];
|
|
476
|
+
return [];
|
|
477
|
+
}
|
|
478
|
+
function fallbackAnalyzePowerShellText(source, file = "<inline>") {
|
|
479
|
+
const stripped = stripPowerShellComments(source);
|
|
480
|
+
const issues = bracketIssues(stripped, file);
|
|
481
|
+
const banned = [
|
|
482
|
+
[/\bInvoke-Expression\b|\biex\b/i, "Invoke-Expression/iex dynamic execution"],
|
|
483
|
+
[/\bSet-ExecutionPolicy\b[\s\S]{0,80}\bBypass\b/i, "Set-ExecutionPolicy Bypass"],
|
|
484
|
+
[/\bStart-Process\b[\s\S]{0,160}\b-Verb\s+RunAs\b/i, "Start-Process -Verb RunAs elevation"],
|
|
485
|
+
[/\bNew-Object\s+Net\.WebClient\b|\bDownloadString\s*\(/i, "legacy WebClient/DownloadString network execution"]
|
|
486
|
+
];
|
|
487
|
+
for (const [re2, label] of banned) {
|
|
488
|
+
if (re2.test(stripped)) issues.push(`${file}: banned PowerShell pattern: ${label}`);
|
|
489
|
+
}
|
|
490
|
+
return issues;
|
|
491
|
+
}
|
|
492
|
+
function runFallback(files, t0, reason) {
|
|
493
|
+
const issues = [];
|
|
494
|
+
for (const file of files) {
|
|
495
|
+
try {
|
|
496
|
+
issues.push(...fallbackAnalyzePowerShellText(readFileSync(file, "utf8"), file));
|
|
497
|
+
} catch (e) {
|
|
498
|
+
issues.push(`${file}: could not read script (${e.message || e})`);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
if (issues.length > 0) {
|
|
502
|
+
return {
|
|
503
|
+
name: "psscriptanalyzer",
|
|
504
|
+
status: "FAIL",
|
|
505
|
+
message: `PowerShell fallback found issues in ${files.length} script(s)`,
|
|
506
|
+
details: [reason, ...issues].slice(0, 20),
|
|
507
|
+
durationMs: Date.now() - t0
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
return {
|
|
511
|
+
name: "psscriptanalyzer",
|
|
512
|
+
status: "PASS",
|
|
513
|
+
message: `${files.length} PowerShell script(s) clean (static fallback; ${reason})`,
|
|
514
|
+
details: [],
|
|
515
|
+
durationMs: Date.now() - t0
|
|
516
|
+
};
|
|
517
|
+
}
|
|
400
518
|
async function run4(ctx) {
|
|
401
519
|
const t0 = Date.now();
|
|
402
520
|
const files = findPs1Files(ctx.repoRoot);
|
|
@@ -411,14 +529,15 @@ async function run4(ctx) {
|
|
|
411
529
|
}
|
|
412
530
|
const which = spawnSync4("pwsh", ["--version"], { encoding: "utf8" });
|
|
413
531
|
if (which.status === null || which.error) {
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
532
|
+
return runFallback(files, t0, "pwsh unavailable");
|
|
533
|
+
}
|
|
534
|
+
const moduleCheck = spawnSync4(
|
|
535
|
+
"pwsh",
|
|
536
|
+
["-NoProfile", "-NonInteractive", "-Command", "if (Get-Module -ListAvailable -Name PSScriptAnalyzer) { exit 0 } else { exit 3 }"],
|
|
537
|
+
{ encoding: "utf8", cwd: ctx.repoRoot, timeout: 1e4 }
|
|
538
|
+
);
|
|
539
|
+
if (moduleCheck.status !== 0) {
|
|
540
|
+
return runFallback(files, t0, "PSScriptAnalyzer module unavailable");
|
|
422
541
|
}
|
|
423
542
|
const script = `
|
|
424
543
|
$files = @(${files.map((f) => `'${f.replace(/'/g, "''")}'`).join(",")})
|
|
@@ -450,10 +569,9 @@ if ($found) { exit 1 } else { exit 0 }
|
|
|
450
569
|
};
|
|
451
570
|
}
|
|
452
571
|
const lines = ((res.stdout || "") + (res.stderr || "")).split("\n").filter(Boolean);
|
|
453
|
-
const isWin = platform() === "win32";
|
|
454
572
|
return {
|
|
455
573
|
name: "psscriptanalyzer",
|
|
456
|
-
status:
|
|
574
|
+
status: "FAIL",
|
|
457
575
|
message: `PSScriptAnalyzer found issues in ${files.length} script(s)`,
|
|
458
576
|
details: lines.slice(0, 20),
|
|
459
577
|
durationMs
|
|
@@ -578,25 +696,47 @@ __export(audit_ci_exports, {
|
|
|
578
696
|
});
|
|
579
697
|
import { spawnSync as spawnSync7 } from "node:child_process";
|
|
580
698
|
import { join as join4 } from "node:path";
|
|
699
|
+
function parseAuditReport(output) {
|
|
700
|
+
const start = output.indexOf("{");
|
|
701
|
+
if (start === -1) return null;
|
|
702
|
+
try {
|
|
703
|
+
return JSON.parse(output.slice(start));
|
|
704
|
+
} catch {
|
|
705
|
+
return null;
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
function highCriticalCount(report) {
|
|
709
|
+
const vulns = report?.metadata?.vulnerabilities || {};
|
|
710
|
+
return Number(vulns.high || 0) + Number(vulns.critical || 0);
|
|
711
|
+
}
|
|
712
|
+
function vulnerableNames(report) {
|
|
713
|
+
const out = [];
|
|
714
|
+
for (const [name12, vuln] of Object.entries(report?.vulnerabilities || {})) {
|
|
715
|
+
const severity12 = String(vuln?.severity || "").toLowerCase();
|
|
716
|
+
if (severity12 === "high" || severity12 === "critical") out.push(`${name12}: ${severity12}`);
|
|
717
|
+
}
|
|
718
|
+
return out;
|
|
719
|
+
}
|
|
581
720
|
async function run7(ctx) {
|
|
582
721
|
const t0 = Date.now();
|
|
583
|
-
const ver = ctx.versions["audit-ci"] || "latest";
|
|
584
|
-
const configPath = join4(ctx.repoRoot, ".audit-ci.jsonc");
|
|
585
722
|
const packageDirs = ["installer", "mcp-server"];
|
|
586
723
|
const runs = packageDirs.map((dir) => {
|
|
587
724
|
const res = spawnSync7(
|
|
588
|
-
"
|
|
589
|
-
["
|
|
725
|
+
"npm",
|
|
726
|
+
["audit", "--audit-level=high", "--json"],
|
|
590
727
|
{
|
|
591
728
|
encoding: "utf8",
|
|
592
729
|
cwd: join4(ctx.repoRoot, dir),
|
|
593
730
|
timeout: 6e4
|
|
594
731
|
}
|
|
595
732
|
);
|
|
596
|
-
|
|
733
|
+
const output = (res.stdout || "") + (res.stderr || "");
|
|
734
|
+
const report = parseAuditReport(output);
|
|
735
|
+
const highCritical = highCriticalCount(report);
|
|
736
|
+
return { dir, status: res.status, output, report, highCritical };
|
|
597
737
|
});
|
|
598
738
|
const durationMs = Date.now() - t0;
|
|
599
|
-
const failed = runs.filter((r) => r.
|
|
739
|
+
const failed = runs.filter((r) => !r.report || r.highCritical > 0);
|
|
600
740
|
if (failed.length === 0) {
|
|
601
741
|
return {
|
|
602
742
|
name: "audit-ci",
|
|
@@ -608,8 +748,13 @@ async function run7(ctx) {
|
|
|
608
748
|
}
|
|
609
749
|
const lines = [];
|
|
610
750
|
for (const r of failed) {
|
|
611
|
-
|
|
612
|
-
|
|
751
|
+
if (!r.report) {
|
|
752
|
+
lines.push(`${r.dir}: audit report unavailable`);
|
|
753
|
+
lines.push(...r.output.split("\n").filter(Boolean).slice(0, 10));
|
|
754
|
+
continue;
|
|
755
|
+
}
|
|
756
|
+
lines.push(`${r.dir}: ${r.highCritical} high/critical advisory item(s)`);
|
|
757
|
+
lines.push(...vulnerableNames(r.report).slice(0, 10));
|
|
613
758
|
}
|
|
614
759
|
return {
|
|
615
760
|
name: "audit-ci",
|
|
@@ -887,7 +1032,7 @@ __export(upgrade_smoke_exports, {
|
|
|
887
1032
|
severity: () => severity11
|
|
888
1033
|
});
|
|
889
1034
|
import { spawnSync as spawnSync11 } from "node:child_process";
|
|
890
|
-
import { mkdtempSync as mkdtempSync3, rmSync as rmSync3, mkdirSync as mkdirSync2, writeFileSync as writeFileSync3, readFileSync, existsSync as existsSync2 } from "node:fs";
|
|
1035
|
+
import { mkdtempSync as mkdtempSync3, rmSync as rmSync3, mkdirSync as mkdirSync2, writeFileSync as writeFileSync3, readFileSync as readFileSync2, existsSync as existsSync2 } from "node:fs";
|
|
891
1036
|
import { join as join7, resolve as resolve2 } from "node:path";
|
|
892
1037
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
893
1038
|
async function run11(ctx) {
|
|
@@ -974,7 +1119,7 @@ async function run11(ctx) {
|
|
|
974
1119
|
if (existsSync2(settingsPath)) {
|
|
975
1120
|
let settings;
|
|
976
1121
|
try {
|
|
977
|
-
settings = JSON.parse(
|
|
1122
|
+
settings = JSON.parse(readFileSync2(settingsPath, "utf8"));
|
|
978
1123
|
} catch (e) {
|
|
979
1124
|
return {
|
|
980
1125
|
name: "upgrade-smoke",
|
|
@@ -997,7 +1142,7 @@ async function run11(ctx) {
|
|
|
997
1142
|
}
|
|
998
1143
|
const marketplaceSrc = join7(installerDir, "src", "marketplace.js");
|
|
999
1144
|
if (existsSync2(marketplaceSrc)) {
|
|
1000
|
-
const src =
|
|
1145
|
+
const src = readFileSync2(marketplaceSrc, "utf8");
|
|
1001
1146
|
const registersCorrectKey = src.includes("'ijfw@ijfw'") || src.includes('"ijfw@ijfw"');
|
|
1002
1147
|
const registersWrongKey = /enabledPlugins\[['"]ijfw-core@ijfw['"]\]\s*=\s*true/.test(src);
|
|
1003
1148
|
if (!registersCorrectKey) {
|
|
@@ -1052,7 +1197,7 @@ var preflight_exports = {};
|
|
|
1052
1197
|
__export(preflight_exports, {
|
|
1053
1198
|
runPreflightCommand: () => runPreflightCommand
|
|
1054
1199
|
});
|
|
1055
|
-
import { readFileSync as
|
|
1200
|
+
import { readFileSync as readFileSync3, existsSync as existsSync3 } from "node:fs";
|
|
1056
1201
|
import { join as join8, dirname, resolve as resolve3 } from "node:path";
|
|
1057
1202
|
import { fileURLToPath } from "node:url";
|
|
1058
1203
|
function printHelp() {
|
|
@@ -1096,7 +1241,7 @@ function loadVersions(repoRoot2) {
|
|
|
1096
1241
|
for (const f of candidates) {
|
|
1097
1242
|
if (existsSync3(f)) {
|
|
1098
1243
|
try {
|
|
1099
|
-
return JSON.parse(
|
|
1244
|
+
return JSON.parse(readFileSync3(f, "utf8"));
|
|
1100
1245
|
} catch {
|
|
1101
1246
|
}
|
|
1102
1247
|
}
|
|
@@ -2435,8 +2580,8 @@ Please report this to https://github.com/markedjs/marked.`, e) {
|
|
|
2435
2580
|
// src/ijfw.js
|
|
2436
2581
|
import { dirname as dirname2, join as join9, resolve as resolve4, basename } from "node:path";
|
|
2437
2582
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
2438
|
-
import { existsSync as existsSync4, mkdirSync as mkdirSync3, copyFileSync, readdirSync as readdirSync4, rmSync as rmSync4, readFileSync as
|
|
2439
|
-
import { homedir, platform
|
|
2583
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync3, copyFileSync, readdirSync as readdirSync4, rmSync as rmSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "node:fs";
|
|
2584
|
+
import { homedir, platform } from "node:os";
|
|
2440
2585
|
import { spawnSync as spawnSync12 } from "node:child_process";
|
|
2441
2586
|
var __dirname2 = dirname2(fileURLToPath2(import.meta.url));
|
|
2442
2587
|
function repoRoot() {
|
|
@@ -2447,6 +2592,62 @@ function repoRoot() {
|
|
|
2447
2592
|
}
|
|
2448
2593
|
return process.cwd();
|
|
2449
2594
|
}
|
|
2595
|
+
function findInternalAsset(...rel) {
|
|
2596
|
+
const root = repoRoot();
|
|
2597
|
+
const ijfwHome = join9(homedir(), ".ijfw");
|
|
2598
|
+
const candidates = [join9(root, ...rel), join9(ijfwHome, ...rel)];
|
|
2599
|
+
return candidates.find((p) => existsSync4(p)) || null;
|
|
2600
|
+
}
|
|
2601
|
+
function readDashboardPort() {
|
|
2602
|
+
const portFile = join9(homedir(), ".ijfw", "dashboard.port");
|
|
2603
|
+
try {
|
|
2604
|
+
const port = Number.parseInt(readFileSync4(portFile, "utf8").trim(), 10);
|
|
2605
|
+
return Number.isFinite(port) ? port : 37891;
|
|
2606
|
+
} catch {
|
|
2607
|
+
return 37891;
|
|
2608
|
+
}
|
|
2609
|
+
}
|
|
2610
|
+
function openBrowser(url) {
|
|
2611
|
+
if (process.env.CI || process.env.NO_OPEN) return;
|
|
2612
|
+
const r = platform() === "darwin" ? spawnSync12("open", [url], { stdio: "ignore" }) : platform() === "win32" ? spawnSync12("cmd", ["/c", "start", "", url], { stdio: "ignore", shell: false }) : spawnSync12("xdg-open", [url], { stdio: "ignore" });
|
|
2613
|
+
return r.status ?? 0;
|
|
2614
|
+
}
|
|
2615
|
+
var ORCHESTRATOR_COMMANDS = /* @__PURE__ */ new Set([
|
|
2616
|
+
"update",
|
|
2617
|
+
"statusline",
|
|
2618
|
+
"config",
|
|
2619
|
+
"insight",
|
|
2620
|
+
"blackboard",
|
|
2621
|
+
"team",
|
|
2622
|
+
"swarm",
|
|
2623
|
+
"codex",
|
|
2624
|
+
"recover",
|
|
2625
|
+
"memory",
|
|
2626
|
+
"cross",
|
|
2627
|
+
"status",
|
|
2628
|
+
"demo",
|
|
2629
|
+
"import",
|
|
2630
|
+
"receipt",
|
|
2631
|
+
"--purge-receipts",
|
|
2632
|
+
"workflow",
|
|
2633
|
+
"handoff",
|
|
2634
|
+
"compress",
|
|
2635
|
+
"consolidate",
|
|
2636
|
+
"cross-audit",
|
|
2637
|
+
"cross-critique",
|
|
2638
|
+
"cross-research",
|
|
2639
|
+
"ijfw-audit",
|
|
2640
|
+
"ijfw-execute",
|
|
2641
|
+
"ijfw-help",
|
|
2642
|
+
"ijfw-plan",
|
|
2643
|
+
"ijfw-ship",
|
|
2644
|
+
"ijfw-verify",
|
|
2645
|
+
"memory-audit",
|
|
2646
|
+
"memory-consent",
|
|
2647
|
+
"memory-why",
|
|
2648
|
+
"metrics",
|
|
2649
|
+
"mode"
|
|
2650
|
+
]);
|
|
2450
2651
|
function printHelp2() {
|
|
2451
2652
|
console.log(`
|
|
2452
2653
|
ijfw -- the AI efficiency layer
|
|
@@ -2460,7 +2661,13 @@ COMMANDS
|
|
|
2460
2661
|
help Open the full IJFW guide (terminal, or --browser for rendered)
|
|
2461
2662
|
preflight Run 11-gate quality pipeline before publishing
|
|
2462
2663
|
dashboard Start / stop / check the local observability dashboard
|
|
2463
|
-
design Manage
|
|
2664
|
+
design Manage live previews and durable design intelligence
|
|
2665
|
+
blackboard Coordinate project-local swarm state and artifact claims
|
|
2666
|
+
codex Check and sync Codex-native IJFW surfaces
|
|
2667
|
+
team Assemble project agents, charter, and workflow manifest
|
|
2668
|
+
swarm Plan, prepare, and track artifact-aware parallel work
|
|
2669
|
+
recover Show latest checkpoint and next recovery step
|
|
2670
|
+
cross Run Trident audit/research/critique, e.g. ijfw cross audit README.md
|
|
2464
2671
|
doctor Diagnose IJFW installation health
|
|
2465
2672
|
|
|
2466
2673
|
--help, -h Show this help
|
|
@@ -2498,7 +2705,7 @@ async function main() {
|
|
|
2498
2705
|
if (delegateToCli(argv.slice(2))) return;
|
|
2499
2706
|
try {
|
|
2500
2707
|
const pkgPath = join9(__dirname2, "..", "package.json");
|
|
2501
|
-
const pkg = JSON.parse(
|
|
2708
|
+
const pkg = JSON.parse(readFileSync4(pkgPath, "utf8"));
|
|
2502
2709
|
console.log(`@ijfw/install@${pkg.version || "unknown"}`);
|
|
2503
2710
|
if (verbose) {
|
|
2504
2711
|
console.log(" (full --verbose details require a completed install: run ijfw install)");
|
|
@@ -2508,11 +2715,14 @@ async function main() {
|
|
|
2508
2715
|
}
|
|
2509
2716
|
process.exit(0);
|
|
2510
2717
|
}
|
|
2511
|
-
if (sub
|
|
2718
|
+
if (ORCHESTRATOR_COMMANDS.has(sub)) {
|
|
2512
2719
|
if (delegateToCli(argv.slice(2))) return;
|
|
2513
2720
|
console.error(`'ijfw ${sub}' requires a completed IJFW install. Run: ijfw install`);
|
|
2514
2721
|
process.exit(1);
|
|
2515
2722
|
}
|
|
2723
|
+
if (sub === "doctor" && findCli()) {
|
|
2724
|
+
if (delegateToCli(argv.slice(2))) return;
|
|
2725
|
+
}
|
|
2516
2726
|
switch (sub) {
|
|
2517
2727
|
case "install": {
|
|
2518
2728
|
const installBin = resolve4(__dirname2, "..", "dist", "install.js");
|
|
@@ -2533,19 +2743,13 @@ async function main() {
|
|
|
2533
2743
|
}
|
|
2534
2744
|
case "dashboard": {
|
|
2535
2745
|
const dashSub = argv[3];
|
|
2536
|
-
const root = repoRoot();
|
|
2537
|
-
const ijfwHome = join9(homedir(), ".ijfw");
|
|
2538
|
-
const findInTree = (...rel) => {
|
|
2539
|
-
const candidates = [join9(root, ...rel), join9(ijfwHome, ...rel)];
|
|
2540
|
-
return candidates.find((p) => existsSync4(p)) || null;
|
|
2541
|
-
};
|
|
2542
2746
|
if (dashSub === "start" || dashSub === "stop" || dashSub === "status") {
|
|
2543
|
-
const dashBin =
|
|
2747
|
+
const dashBin = findInternalAsset("mcp-server", "bin", "ijfw-dashboard");
|
|
2544
2748
|
if (dashBin) {
|
|
2545
2749
|
const r = spawnSync12("node", [dashBin, dashSub, ...argv.slice(4)], { stdio: "inherit" });
|
|
2546
2750
|
process.exit(r.status ?? 0);
|
|
2547
2751
|
} else {
|
|
2548
|
-
const serverJs =
|
|
2752
|
+
const serverJs = findInternalAsset("mcp-server", "src", "dashboard-server.js");
|
|
2549
2753
|
if (dashSub === "start" && serverJs) {
|
|
2550
2754
|
const { spawn } = await import("node:child_process");
|
|
2551
2755
|
const child = spawn(process.execPath, [serverJs, "start", "--daemon"], {
|
|
@@ -2560,7 +2764,7 @@ async function main() {
|
|
|
2560
2764
|
process.exit(1);
|
|
2561
2765
|
}
|
|
2562
2766
|
} else if (dashSub === "render" || !dashSub) {
|
|
2563
|
-
const binJs =
|
|
2767
|
+
const binJs = findInternalAsset("scripts", "dashboard", "bin.js");
|
|
2564
2768
|
if (binJs) {
|
|
2565
2769
|
const r = spawnSync12("node", [binJs, ...argv.slice(dashSub ? 4 : 3)], { stdio: "inherit" });
|
|
2566
2770
|
process.exit(r.status ?? 0);
|
|
@@ -2576,30 +2780,72 @@ async function main() {
|
|
|
2576
2780
|
}
|
|
2577
2781
|
case "design": {
|
|
2578
2782
|
const designSub = argv[3];
|
|
2783
|
+
const durableDesign = ["init", "plan", "audit", "critique", "polish", "normalize", "bolder", "quieter", "handoff"];
|
|
2784
|
+
if (durableDesign.includes(designSub)) {
|
|
2785
|
+
if (delegateToCli(argv.slice(2))) return;
|
|
2786
|
+
console.error(`'ijfw design ${designSub}' requires a completed IJFW install. Run: ijfw install`);
|
|
2787
|
+
process.exit(1);
|
|
2788
|
+
}
|
|
2579
2789
|
const contentDir = join9(homedir(), ".ijfw", "design-companion", "content");
|
|
2580
2790
|
mkdirSync3(contentDir, { recursive: true });
|
|
2581
|
-
if (designSub === "
|
|
2582
|
-
const
|
|
2583
|
-
if (!
|
|
2584
|
-
console.error("
|
|
2791
|
+
if (designSub === "start" || designSub === "open") {
|
|
2792
|
+
const dashBin = findInternalAsset("mcp-server", "bin", "ijfw-dashboard");
|
|
2793
|
+
if (!dashBin) {
|
|
2794
|
+
console.error("[ijfw] Design companion server not found. Run `ijfw-install` to deploy ~/.ijfw/, or run from the IJFW repo root.");
|
|
2795
|
+
process.exit(1);
|
|
2796
|
+
}
|
|
2797
|
+
const noOpen = argv.slice(4).includes("--no-open");
|
|
2798
|
+
const r = spawnSync12("node", [dashBin, "start", "--no-open"], { stdio: designSub === "start" ? "inherit" : "ignore" });
|
|
2799
|
+
if ((r.status ?? 1) !== 0) process.exit(r.status ?? 1);
|
|
2800
|
+
const url = `http://localhost:${readDashboardPort()}/design`;
|
|
2801
|
+
if (!noOpen) openBrowser(url);
|
|
2802
|
+
console.log(`Design companion running at ${url}`);
|
|
2803
|
+
} else if (designSub === "status") {
|
|
2804
|
+
const dashBin = findInternalAsset("mcp-server", "bin", "ijfw-dashboard");
|
|
2805
|
+
if (!dashBin) {
|
|
2806
|
+
console.error("[ijfw] Design companion server not found. Run `ijfw-install` to deploy ~/.ijfw/, or run from the IJFW repo root.");
|
|
2585
2807
|
process.exit(1);
|
|
2586
2808
|
}
|
|
2587
|
-
const
|
|
2588
|
-
if (
|
|
2589
|
-
|
|
2809
|
+
const r = spawnSync12("node", [dashBin, "status"], { stdio: "inherit" });
|
|
2810
|
+
if ((r.status ?? 1) === 0) console.log(`Design companion URL: http://localhost:${readDashboardPort()}/design`);
|
|
2811
|
+
process.exit(r.status ?? 0);
|
|
2812
|
+
} else if (designSub === "stop") {
|
|
2813
|
+
const dashBin = findInternalAsset("mcp-server", "bin", "ijfw-dashboard");
|
|
2814
|
+
if (!dashBin) {
|
|
2815
|
+
console.error("[ijfw] Design companion server not found. Run `ijfw-install` to deploy ~/.ijfw/, or run from the IJFW repo root.");
|
|
2590
2816
|
process.exit(1);
|
|
2591
2817
|
}
|
|
2592
|
-
const
|
|
2593
|
-
|
|
2594
|
-
|
|
2818
|
+
const r = spawnSync12("node", [dashBin, "stop"], { stdio: "inherit" });
|
|
2819
|
+
process.exit(r.status ?? 0);
|
|
2820
|
+
} else if (designSub === "push") {
|
|
2821
|
+
const filePaths = argv.slice(4);
|
|
2822
|
+
if (filePaths.length === 0) {
|
|
2823
|
+
console.error("Usage: ijfw design push <file.html> [more.html ...]");
|
|
2824
|
+
process.exit(1);
|
|
2825
|
+
}
|
|
2826
|
+
for (const filePath of filePaths) {
|
|
2827
|
+
const abs = resolve4(filePath);
|
|
2828
|
+
if (!abs.toLowerCase().endsWith(".html")) {
|
|
2829
|
+
console.error("Design companion accepts standalone .html files.");
|
|
2830
|
+
process.exit(1);
|
|
2831
|
+
}
|
|
2832
|
+
if (!existsSync4(abs)) {
|
|
2833
|
+
console.error(`File not found: ${abs}`);
|
|
2834
|
+
process.exit(1);
|
|
2835
|
+
}
|
|
2836
|
+
const dest = join9(contentDir, basename(abs));
|
|
2837
|
+
copyFileSync(abs, dest);
|
|
2838
|
+
console.log(`Design pushed: ${dest}`);
|
|
2839
|
+
}
|
|
2840
|
+
console.log(`Preview: http://localhost:${readDashboardPort()}/design`);
|
|
2595
2841
|
} else if (designSub === "clear") {
|
|
2596
2842
|
const files = readdirSync4(contentDir);
|
|
2597
2843
|
for (const f of files) rmSync4(join9(contentDir, f), { force: true });
|
|
2598
2844
|
console.log("Design companion content cleared.");
|
|
2599
2845
|
} else {
|
|
2600
|
-
console.log("ijfw design -- Manage
|
|
2846
|
+
console.log("ijfw design -- Manage live preview and durable design intelligence.");
|
|
2601
2847
|
console.log("");
|
|
2602
|
-
console.log("Usage: ijfw design push <file.html> |
|
|
2848
|
+
console.log("Usage: ijfw design start [--no-open] | open | status | stop | push <file.html> [more.html ...] | clear | init|plan|audit|critique|polish|normalize|bolder|quieter|handoff");
|
|
2603
2849
|
process.exit(1);
|
|
2604
2850
|
}
|
|
2605
2851
|
break;
|
|
@@ -2626,7 +2872,7 @@ async function main() {
|
|
|
2626
2872
|
copyFileSync(join9(assetsSrc, f), join9(outDir, "assets", f));
|
|
2627
2873
|
}
|
|
2628
2874
|
}
|
|
2629
|
-
const md =
|
|
2875
|
+
const md = readFileSync4(guidePath, "utf8").replace(/\(guide\/assets\//g, "(assets/");
|
|
2630
2876
|
const rendered = marked.parse(md, { gfm: true, breaks: false });
|
|
2631
2877
|
const html = `<!doctype html>
|
|
2632
2878
|
<html lang="en"><head>
|
|
@@ -2645,7 +2891,7 @@ async function main() {
|
|
|
2645
2891
|
</head><body><div class="wrap markdown-body">${rendered}</div></body></html>`;
|
|
2646
2892
|
const outHtml = join9(outDir, "index.html");
|
|
2647
2893
|
writeFileSync4(outHtml, html);
|
|
2648
|
-
const opener =
|
|
2894
|
+
const opener = platform() === "darwin" ? "open" : platform() === "win32" ? "start" : "xdg-open";
|
|
2649
2895
|
spawnSync12(opener, [outHtml], { stdio: "ignore", detached: true });
|
|
2650
2896
|
console.log(`[ijfw] Guide opened in your browser.`);
|
|
2651
2897
|
console.log(` Local copy: ${outHtml}`);
|
|
@@ -2655,10 +2901,10 @@ async function main() {
|
|
|
2655
2901
|
if (hasLess) {
|
|
2656
2902
|
const lessRes = spawnSync12("less", ["-R", guidePath], { stdio: "inherit" });
|
|
2657
2903
|
if (lessRes.status !== 0 && lessRes.status !== null) {
|
|
2658
|
-
process.stdout.write(
|
|
2904
|
+
process.stdout.write(readFileSync4(guidePath, "utf8"));
|
|
2659
2905
|
}
|
|
2660
2906
|
} else {
|
|
2661
|
-
process.stdout.write(
|
|
2907
|
+
process.stdout.write(readFileSync4(guidePath, "utf8"));
|
|
2662
2908
|
}
|
|
2663
2909
|
process.exit(0);
|
|
2664
2910
|
break;
|
package/dist/install.js
CHANGED
|
@@ -385,7 +385,7 @@ function mergeYamlPluginsEnabled(dst, pluginName, ts) {
|
|
|
385
385
|
if (inPluginsBlock) {
|
|
386
386
|
if (/^\S/.test(line) && line.trim() !== "") {
|
|
387
387
|
inPluginsBlock = false;
|
|
388
|
-
} else if (
|
|
388
|
+
} else if (isIndentedEnabledLine(line)) {
|
|
389
389
|
enabledLineIdx = i;
|
|
390
390
|
} else if (enabledLineIdx >= 0 && itemRe.test(line)) {
|
|
391
391
|
alreadyListed = true;
|
|
@@ -423,6 +423,11 @@ function mergeYamlPluginsEnabled(dst, pluginName, ts) {
|
|
|
423
423
|
outText += "# IJFW-PLUGINS-END\n";
|
|
424
424
|
writeAtomic(dst, outText, { mode: 384 });
|
|
425
425
|
}
|
|
426
|
+
function isIndentedEnabledLine(line) {
|
|
427
|
+
if (!line || !/\s/.test(line[0])) return false;
|
|
428
|
+
const trimmed = line.trim();
|
|
429
|
+
return trimmed === "enabled:" || trimmed === "enabled: []" || trimmed.startsWith("enabled: [") && trimmed.endsWith("]");
|
|
430
|
+
}
|
|
426
431
|
function opencodeMerge(dst, serverJs, ts) {
|
|
427
432
|
mkdirSync2(dirname3(dst), { recursive: true });
|
|
428
433
|
if (ts) backup(dst, ts);
|
|
@@ -759,6 +764,12 @@ async function installCodex(ctx) {
|
|
|
759
764
|
for (const sd of listSubdirs(repoSkills)) {
|
|
760
765
|
copyDirIfAbsent(sd.path, join4(userSkills, sd.name));
|
|
761
766
|
}
|
|
767
|
+
const userCommands = join4(ctx.home, ".codex", "commands");
|
|
768
|
+
ensureDir(userCommands);
|
|
769
|
+
const repoCommands = join4(ctx.repoRoot, "codex", "commands");
|
|
770
|
+
for (const f of listFiles(repoCommands, ".md")) {
|
|
771
|
+
copyIfAbsent(f.path, join4(userCommands, f.name));
|
|
772
|
+
}
|
|
762
773
|
const cwd = ctx.cwd || process.cwd();
|
|
763
774
|
if (existsSync4(join4(cwd, ".codex", "config.toml")) || existsSync4(join4(cwd, ".ijfw"))) {
|
|
764
775
|
const projSkills = join4(cwd, ".codex", "skills");
|
|
@@ -766,8 +777,13 @@ async function installCodex(ctx) {
|
|
|
766
777
|
for (const sd of listSubdirs(repoSkills)) {
|
|
767
778
|
copyDirIfAbsent(sd.path, join4(projSkills, sd.name));
|
|
768
779
|
}
|
|
780
|
+
const projCommands = join4(cwd, ".codex", "commands");
|
|
781
|
+
ensureDir(projCommands);
|
|
782
|
+
for (const f of listFiles(repoCommands, ".md")) {
|
|
783
|
+
copyIfAbsent(f.path, join4(projCommands, f.name));
|
|
784
|
+
}
|
|
769
785
|
}
|
|
770
|
-
ctx.log.ok("Installed Codex bundle: MCP + hooks + 19 skills + context");
|
|
786
|
+
ctx.log.ok("Installed Codex bundle: MCP + hooks + 19 skills + 22 command aliases + context");
|
|
771
787
|
return { status: "ok" };
|
|
772
788
|
}
|
|
773
789
|
async function installGemini(ctx) {
|
package/dist/uninstall.js
CHANGED
|
@@ -241,9 +241,11 @@ import os; os.replace(p + ".tmp", p)
|
|
|
241
241
|
return true;
|
|
242
242
|
}
|
|
243
243
|
const stripped = raw.replace(
|
|
244
|
+
// eslint-disable-next-line security/detect-unsafe-regex -- raw is a small local YAML config file; pattern is line-anchored to the IJFW-owned block.
|
|
244
245
|
/^ ijfw-memory:\n(?: .*\n)*(?:# IJFW-MCP-END ijfw-memory\n)?/m,
|
|
245
246
|
""
|
|
246
247
|
).replace(
|
|
248
|
+
// eslint-disable-next-line security/detect-unsafe-regex -- raw is a small local YAML config file; pattern is bounded by exact IJFW sentinel markers.
|
|
247
249
|
/# IJFW-MCP-BEGIN ijfw-memory\n(?:.*\n)*?# IJFW-MCP-END ijfw-memory\n/,
|
|
248
250
|
""
|
|
249
251
|
);
|
|
@@ -263,6 +265,42 @@ function removeIjfwSkills(dir) {
|
|
|
263
265
|
}
|
|
264
266
|
return count;
|
|
265
267
|
}
|
|
268
|
+
var CODEX_COMMAND_FILES = [
|
|
269
|
+
"compress.md",
|
|
270
|
+
"consolidate.md",
|
|
271
|
+
"cross-audit.md",
|
|
272
|
+
"cross-critique.md",
|
|
273
|
+
"cross-research.md",
|
|
274
|
+
"doctor.md",
|
|
275
|
+
"handoff.md",
|
|
276
|
+
"ijfw-audit.md",
|
|
277
|
+
"ijfw-execute.md",
|
|
278
|
+
"ijfw-help.md",
|
|
279
|
+
"ijfw-plan.md",
|
|
280
|
+
"ijfw-ship.md",
|
|
281
|
+
"ijfw-verify.md",
|
|
282
|
+
"ijfw.md",
|
|
283
|
+
"memory-audit.md",
|
|
284
|
+
"memory-consent.md",
|
|
285
|
+
"memory-why.md",
|
|
286
|
+
"metrics.md",
|
|
287
|
+
"mode.md",
|
|
288
|
+
"status.md",
|
|
289
|
+
"team.md",
|
|
290
|
+
"workflow.md"
|
|
291
|
+
];
|
|
292
|
+
function removeCodexCommands(dir) {
|
|
293
|
+
if (!existsSync2(dir)) return 0;
|
|
294
|
+
let count = 0;
|
|
295
|
+
for (const name of CODEX_COMMAND_FILES) {
|
|
296
|
+
const path = join2(dir, name);
|
|
297
|
+
if (existsSync2(path)) {
|
|
298
|
+
rmSync(path, { force: true });
|
|
299
|
+
count++;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
return count;
|
|
303
|
+
}
|
|
266
304
|
function cleanPlatforms() {
|
|
267
305
|
const removed = [];
|
|
268
306
|
if (removeTomlSection(join2(HOME, ".codex", "config.toml"))) {
|
|
@@ -273,6 +311,8 @@ function cleanPlatforms() {
|
|
|
273
311
|
}
|
|
274
312
|
const codexSkills = removeIjfwSkills(join2(HOME, ".codex", "skills"));
|
|
275
313
|
if (codexSkills > 0) removed.push(`~/.codex/skills/ijfw-* (removed ${codexSkills} skill dirs)`);
|
|
314
|
+
const codexCommands = removeCodexCommands(join2(HOME, ".codex", "commands"));
|
|
315
|
+
if (codexCommands > 0) removed.push(`~/.codex/commands (removed ${codexCommands} IJFW command aliases)`);
|
|
276
316
|
const codexMd = join2(HOME, ".codex", "IJFW.md");
|
|
277
317
|
if (existsSync2(codexMd)) {
|
|
278
318
|
rmSync(codexMd, { force: true });
|