@ijfw/install 1.3.0 → 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 +38 -1
- package/dist/ijfw.js +353 -86
- package/dist/install.js +23 -9
- package/dist/uninstall.js +40 -0
- package/docs/GUIDE.md +4 -4
- package/package.json +2 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,42 @@
|
|
|
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
|
+
|
|
21
|
+
## [1.3.1] -- 2026-05-12
|
|
22
|
+
|
|
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.
|
|
24
|
+
|
|
25
|
+
- Codex `SessionStart` now emits `additionalContext` inside `hookSpecificOutput` with `hookEventName: "SessionStart"`, matching Codex's strict hook response shape. This closes the `SessionStart hook (failed): hook returned invalid session start JSON output` error.
|
|
26
|
+
- Codex `PostToolUse` is now stdout-silent. IJFW still records local observations and failure signals, but it no longer re-injects cleaned tool output as `systemMessage`, which Codex renders as visible hook warning spam.
|
|
27
|
+
- Codex hook Node shims now pass user/tool text after `--`, so tool output beginning with `---` is treated as data instead of a Node CLI option. This closes the `node: bad option: ---` failure.
|
|
28
|
+
- Codex `UserPromptSubmit` and `PreToolUse` advisory output now use the same `hookSpecificOutput.additionalContext` shape when they do emit context.
|
|
29
|
+
- Codex `Stop` now stays stdout-silent on routine session saves. Normal receipts are written locally; only actionable compression notices can surface by default. This closes the `Stop hook (completed) warning: [ijfw] Session #... saved` noise.
|
|
30
|
+
- Codex bundle tests now cover strict `SessionStart` JSON and silent `PostToolUse` behavior for both routine output and failure-looking output.
|
|
31
|
+
- The shipped MCP server no longer installs `@xenova/transformers` by default. Cold semantic vectors are explicit opt-in and `IJFW_VECTORS` now defaults to `off`, keeping the production install smaller and quieter under package audits.
|
|
32
|
+
- The preflight audit gate now runs against both `installer` and `mcp-server`, so server-side dependency drift is covered by the same release gate as the installer.
|
|
33
|
+
- `ijfw preflight` now routes through the canonical 11-gate preflight entrypoint from both installer and MCP CLI paths instead of falling back to `scripts/check-all.sh`.
|
|
34
|
+
- Platform capability drift is now gated by `platform-capabilities.json` plus `scripts/check-platform-drift.js`, covering skill counts, hook-event counts, and marketplace/plugin manifest claims.
|
|
35
|
+
- Update-check changelog URLs now point at GitLab releases, matching the canonical repository.
|
|
36
|
+
- D2 graph-write lock timeout handling now throws the intended `EBUSY_GRAPH_WRITE` error instead of tripping an undeclared variable path under collision.
|
|
37
|
+
|
|
38
|
+
Files: `codex/.codex/hooks/session-start.sh`, `codex/.codex/hooks/post-tool-use.sh`, `codex/.codex/hooks/pre-prompt.sh`, `codex/.codex/hooks/pre-tool-use.sh`, `codex/.codex/hooks/session-end.sh`, `scripts/observation/test-envelope-invariant-codex.sh`, `mcp-server/test-codex-bundle.js`, `mcp-server/src/vectors.js`, `mcp-server/package.json`, `installer/src/preflight/gates/audit-ci.js`, `installer/src/preflight.js`, `mcp-server/src/cross-orchestrator-cli.js`, `mcp-server/src/compute/graph-lock.js`, `platform-capabilities.json`, `scripts/check-platform-drift.js`.
|
|
39
|
+
|
|
3
40
|
## [1.2.6] -- 2026-05-01
|
|
4
41
|
|
|
5
42
|
**Token sandbox + parallel workflow dispatch + DeepSeek frontier upgrade.** A new `ijfw_run` MCP tool keeps large command output out of your context window entirely -- builds, test suites, grep runs, and log tails are sandboxed to disk and summarized in a few lines instead of flooding thousands of tokens. The `ijfw-workflow` execution engine gains a formal Wave Table that makes parallel agent dispatch deterministic rather than inferred. DeepSeek moves to `deepseek-v4-pro` -- the actual frontier model -- so the Trident gets Frontier AI checking Frontier AI.
|
|
@@ -640,7 +677,7 @@ Platform count: **8 install targets -> 13 MCP-integrated + 1 rules-only**. Same
|
|
|
640
677
|
|
|
641
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.
|
|
642
679
|
- Blocking vs advisory distinction: exit 0 when all blocking gates pass even if advisory warnings exist. Exit 1 on any blocking failure.
|
|
643
|
-
-
|
|
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.
|
|
644
681
|
- Warm-cache SLO: <=90s. Cold-cache: <=240s. Both printed in the summary line.
|
|
645
682
|
- `prepublishOnly` in `installer/package.json` now runs preflight before every publish so no tag can ship with a blocking gate open.
|
|
646
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,31 +696,66 @@ __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
|
|
584
|
-
const
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
722
|
+
const packageDirs = ["installer", "mcp-server"];
|
|
723
|
+
const runs = packageDirs.map((dir) => {
|
|
724
|
+
const res = spawnSync7(
|
|
725
|
+
"npm",
|
|
726
|
+
["audit", "--audit-level=high", "--json"],
|
|
727
|
+
{
|
|
728
|
+
encoding: "utf8",
|
|
729
|
+
cwd: join4(ctx.repoRoot, dir),
|
|
730
|
+
timeout: 6e4
|
|
731
|
+
}
|
|
732
|
+
);
|
|
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 };
|
|
737
|
+
});
|
|
594
738
|
const durationMs = Date.now() - t0;
|
|
595
|
-
const
|
|
596
|
-
|
|
597
|
-
if (res.status === 0) {
|
|
739
|
+
const failed = runs.filter((r) => !r.report || r.highCritical > 0);
|
|
740
|
+
if (failed.length === 0) {
|
|
598
741
|
return {
|
|
599
742
|
name: "audit-ci",
|
|
600
743
|
status: "PASS",
|
|
601
|
-
message: "audit-ci: no high/critical vulnerabilities",
|
|
602
|
-
details:
|
|
744
|
+
message: "audit-ci: no high/critical vulnerabilities in installer or mcp-server",
|
|
745
|
+
details: runs.map((r) => `${r.dir}: pass`),
|
|
603
746
|
durationMs
|
|
604
747
|
};
|
|
605
748
|
}
|
|
749
|
+
const lines = [];
|
|
750
|
+
for (const r of failed) {
|
|
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));
|
|
758
|
+
}
|
|
606
759
|
return {
|
|
607
760
|
name: "audit-ci",
|
|
608
761
|
status: "FAIL",
|
|
@@ -879,7 +1032,7 @@ __export(upgrade_smoke_exports, {
|
|
|
879
1032
|
severity: () => severity11
|
|
880
1033
|
});
|
|
881
1034
|
import { spawnSync as spawnSync11 } from "node:child_process";
|
|
882
|
-
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";
|
|
883
1036
|
import { join as join7, resolve as resolve2 } from "node:path";
|
|
884
1037
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
885
1038
|
async function run11(ctx) {
|
|
@@ -966,7 +1119,7 @@ async function run11(ctx) {
|
|
|
966
1119
|
if (existsSync2(settingsPath)) {
|
|
967
1120
|
let settings;
|
|
968
1121
|
try {
|
|
969
|
-
settings = JSON.parse(
|
|
1122
|
+
settings = JSON.parse(readFileSync2(settingsPath, "utf8"));
|
|
970
1123
|
} catch (e) {
|
|
971
1124
|
return {
|
|
972
1125
|
name: "upgrade-smoke",
|
|
@@ -989,7 +1142,7 @@ async function run11(ctx) {
|
|
|
989
1142
|
}
|
|
990
1143
|
const marketplaceSrc = join7(installerDir, "src", "marketplace.js");
|
|
991
1144
|
if (existsSync2(marketplaceSrc)) {
|
|
992
|
-
const src =
|
|
1145
|
+
const src = readFileSync2(marketplaceSrc, "utf8");
|
|
993
1146
|
const registersCorrectKey = src.includes("'ijfw@ijfw'") || src.includes('"ijfw@ijfw"');
|
|
994
1147
|
const registersWrongKey = /enabledPlugins\[['"]ijfw-core@ijfw['"]\]\s*=\s*true/.test(src);
|
|
995
1148
|
if (!registersCorrectKey) {
|
|
@@ -1044,8 +1197,8 @@ var preflight_exports = {};
|
|
|
1044
1197
|
__export(preflight_exports, {
|
|
1045
1198
|
runPreflightCommand: () => runPreflightCommand
|
|
1046
1199
|
});
|
|
1047
|
-
import { readFileSync as
|
|
1048
|
-
import { join as join8, dirname } from "node:path";
|
|
1200
|
+
import { readFileSync as readFileSync3, existsSync as existsSync3 } from "node:fs";
|
|
1201
|
+
import { join as join8, dirname, resolve as resolve3 } from "node:path";
|
|
1049
1202
|
import { fileURLToPath } from "node:url";
|
|
1050
1203
|
function printHelp() {
|
|
1051
1204
|
console.log(`
|
|
@@ -1088,7 +1241,7 @@ function loadVersions(repoRoot2) {
|
|
|
1088
1241
|
for (const f of candidates) {
|
|
1089
1242
|
if (existsSync3(f)) {
|
|
1090
1243
|
try {
|
|
1091
|
-
return JSON.parse(
|
|
1244
|
+
return JSON.parse(readFileSync3(f, "utf8"));
|
|
1092
1245
|
} catch {
|
|
1093
1246
|
}
|
|
1094
1247
|
}
|
|
@@ -1144,11 +1297,24 @@ async function runPreflightCommand(argv, repoRoot2) {
|
|
|
1144
1297
|
const report = await runPreflight(gates, ctx);
|
|
1145
1298
|
process.exit(report.outcome === "pass" ? 0 : 1);
|
|
1146
1299
|
}
|
|
1300
|
+
function defaultRepoRoot() {
|
|
1301
|
+
let dir = __dirname;
|
|
1302
|
+
for (let i = 0; i < 8; i++) {
|
|
1303
|
+
if (existsSync3(join8(dir, "package.json")) && existsSync3(join8(dir, "mcp-server"))) return dir;
|
|
1304
|
+
const next = resolve3(dir, "..");
|
|
1305
|
+
if (next === dir) break;
|
|
1306
|
+
dir = next;
|
|
1307
|
+
}
|
|
1308
|
+
return process.cwd();
|
|
1309
|
+
}
|
|
1147
1310
|
var __dirname;
|
|
1148
1311
|
var init_preflight = __esm({
|
|
1149
|
-
"src/preflight.js"() {
|
|
1312
|
+
async "src/preflight.js"() {
|
|
1150
1313
|
init_runner();
|
|
1151
1314
|
__dirname = dirname(fileURLToPath(import.meta.url));
|
|
1315
|
+
if (process.argv[1] && resolve3(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
1316
|
+
await runPreflightCommand(process.argv, defaultRepoRoot());
|
|
1317
|
+
}
|
|
1152
1318
|
}
|
|
1153
1319
|
});
|
|
1154
1320
|
|
|
@@ -2412,20 +2578,76 @@ Please report this to https://github.com/markedjs/marked.`, e) {
|
|
|
2412
2578
|
});
|
|
2413
2579
|
|
|
2414
2580
|
// src/ijfw.js
|
|
2415
|
-
import { dirname as dirname2, join as join9, resolve as
|
|
2581
|
+
import { dirname as dirname2, join as join9, resolve as resolve4, basename } from "node:path";
|
|
2416
2582
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
2417
|
-
import { existsSync as existsSync4, mkdirSync as mkdirSync3, copyFileSync, readdirSync as readdirSync4, rmSync as rmSync4, readFileSync as
|
|
2418
|
-
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";
|
|
2419
2585
|
import { spawnSync as spawnSync12 } from "node:child_process";
|
|
2420
2586
|
var __dirname2 = dirname2(fileURLToPath2(import.meta.url));
|
|
2421
2587
|
function repoRoot() {
|
|
2422
2588
|
let dir = __dirname2;
|
|
2423
2589
|
for (let i = 0; i < 6; i++) {
|
|
2424
2590
|
if (existsSync4(join9(dir, "package.json")) && existsSync4(join9(dir, ".git"))) return dir;
|
|
2425
|
-
dir =
|
|
2591
|
+
dir = resolve4(dir, "..");
|
|
2426
2592
|
}
|
|
2427
2593
|
return process.cwd();
|
|
2428
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
|
+
]);
|
|
2429
2651
|
function printHelp2() {
|
|
2430
2652
|
console.log(`
|
|
2431
2653
|
ijfw -- the AI efficiency layer
|
|
@@ -2437,9 +2659,15 @@ COMMANDS
|
|
|
2437
2659
|
install Install IJFW into your AI coding agents
|
|
2438
2660
|
uninstall Remove IJFW from your AI coding agents
|
|
2439
2661
|
help Open the full IJFW guide (terminal, or --browser for rendered)
|
|
2440
|
-
preflight Run
|
|
2662
|
+
preflight Run 11-gate quality pipeline before publishing
|
|
2441
2663
|
dashboard Start / stop / check the local observability dashboard
|
|
2442
|
-
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
|
|
2443
2671
|
doctor Diagnose IJFW installation health
|
|
2444
2672
|
|
|
2445
2673
|
--help, -h Show this help
|
|
@@ -2477,7 +2705,7 @@ async function main() {
|
|
|
2477
2705
|
if (delegateToCli(argv.slice(2))) return;
|
|
2478
2706
|
try {
|
|
2479
2707
|
const pkgPath = join9(__dirname2, "..", "package.json");
|
|
2480
|
-
const pkg = JSON.parse(
|
|
2708
|
+
const pkg = JSON.parse(readFileSync4(pkgPath, "utf8"));
|
|
2481
2709
|
console.log(`@ijfw/install@${pkg.version || "unknown"}`);
|
|
2482
2710
|
if (verbose) {
|
|
2483
2711
|
console.log(" (full --verbose details require a completed install: run ijfw install)");
|
|
@@ -2487,44 +2715,41 @@ async function main() {
|
|
|
2487
2715
|
}
|
|
2488
2716
|
process.exit(0);
|
|
2489
2717
|
}
|
|
2490
|
-
if (sub
|
|
2718
|
+
if (ORCHESTRATOR_COMMANDS.has(sub)) {
|
|
2491
2719
|
if (delegateToCli(argv.slice(2))) return;
|
|
2492
2720
|
console.error(`'ijfw ${sub}' requires a completed IJFW install. Run: ijfw install`);
|
|
2493
2721
|
process.exit(1);
|
|
2494
2722
|
}
|
|
2723
|
+
if (sub === "doctor" && findCli()) {
|
|
2724
|
+
if (delegateToCli(argv.slice(2))) return;
|
|
2725
|
+
}
|
|
2495
2726
|
switch (sub) {
|
|
2496
2727
|
case "install": {
|
|
2497
|
-
const installBin =
|
|
2728
|
+
const installBin = resolve4(__dirname2, "..", "dist", "install.js");
|
|
2498
2729
|
const r = spawnSync12("node", [installBin, ...argv.slice(3)], { stdio: "inherit" });
|
|
2499
2730
|
process.exit(r.status ?? 1);
|
|
2500
2731
|
break;
|
|
2501
2732
|
}
|
|
2502
2733
|
case "uninstall": {
|
|
2503
|
-
const uninstallBin =
|
|
2734
|
+
const uninstallBin = resolve4(__dirname2, "..", "dist", "uninstall.js");
|
|
2504
2735
|
const r = spawnSync12("node", [uninstallBin, ...argv.slice(3)], { stdio: "inherit" });
|
|
2505
2736
|
process.exit(r.status ?? 1);
|
|
2506
2737
|
break;
|
|
2507
2738
|
}
|
|
2508
2739
|
case "preflight": {
|
|
2509
|
-
const { runPreflightCommand: runPreflightCommand2 } = await
|
|
2740
|
+
const { runPreflightCommand: runPreflightCommand2 } = await init_preflight().then(() => preflight_exports);
|
|
2510
2741
|
await runPreflightCommand2([argv[0], argv[1], ...argv.slice(3)], repoRoot());
|
|
2511
2742
|
break;
|
|
2512
2743
|
}
|
|
2513
2744
|
case "dashboard": {
|
|
2514
2745
|
const dashSub = argv[3];
|
|
2515
|
-
const root = repoRoot();
|
|
2516
|
-
const ijfwHome = join9(homedir(), ".ijfw");
|
|
2517
|
-
const findInTree = (...rel) => {
|
|
2518
|
-
const candidates = [join9(root, ...rel), join9(ijfwHome, ...rel)];
|
|
2519
|
-
return candidates.find((p) => existsSync4(p)) || null;
|
|
2520
|
-
};
|
|
2521
2746
|
if (dashSub === "start" || dashSub === "stop" || dashSub === "status") {
|
|
2522
|
-
const dashBin =
|
|
2747
|
+
const dashBin = findInternalAsset("mcp-server", "bin", "ijfw-dashboard");
|
|
2523
2748
|
if (dashBin) {
|
|
2524
2749
|
const r = spawnSync12("node", [dashBin, dashSub, ...argv.slice(4)], { stdio: "inherit" });
|
|
2525
2750
|
process.exit(r.status ?? 0);
|
|
2526
2751
|
} else {
|
|
2527
|
-
const serverJs =
|
|
2752
|
+
const serverJs = findInternalAsset("mcp-server", "src", "dashboard-server.js");
|
|
2528
2753
|
if (dashSub === "start" && serverJs) {
|
|
2529
2754
|
const { spawn } = await import("node:child_process");
|
|
2530
2755
|
const child = spawn(process.execPath, [serverJs, "start", "--daemon"], {
|
|
@@ -2539,7 +2764,7 @@ async function main() {
|
|
|
2539
2764
|
process.exit(1);
|
|
2540
2765
|
}
|
|
2541
2766
|
} else if (dashSub === "render" || !dashSub) {
|
|
2542
|
-
const binJs =
|
|
2767
|
+
const binJs = findInternalAsset("scripts", "dashboard", "bin.js");
|
|
2543
2768
|
if (binJs) {
|
|
2544
2769
|
const r = spawnSync12("node", [binJs, ...argv.slice(dashSub ? 4 : 3)], { stdio: "inherit" });
|
|
2545
2770
|
process.exit(r.status ?? 0);
|
|
@@ -2555,30 +2780,72 @@ async function main() {
|
|
|
2555
2780
|
}
|
|
2556
2781
|
case "design": {
|
|
2557
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
|
+
}
|
|
2558
2789
|
const contentDir = join9(homedir(), ".ijfw", "design-companion", "content");
|
|
2559
2790
|
mkdirSync3(contentDir, { recursive: true });
|
|
2560
|
-
if (designSub === "
|
|
2561
|
-
const
|
|
2562
|
-
if (!
|
|
2563
|
-
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.");
|
|
2807
|
+
process.exit(1);
|
|
2808
|
+
}
|
|
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.");
|
|
2564
2816
|
process.exit(1);
|
|
2565
2817
|
}
|
|
2566
|
-
const
|
|
2567
|
-
|
|
2568
|
-
|
|
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 ...]");
|
|
2569
2824
|
process.exit(1);
|
|
2570
2825
|
}
|
|
2571
|
-
const
|
|
2572
|
-
|
|
2573
|
-
|
|
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`);
|
|
2574
2841
|
} else if (designSub === "clear") {
|
|
2575
2842
|
const files = readdirSync4(contentDir);
|
|
2576
2843
|
for (const f of files) rmSync4(join9(contentDir, f), { force: true });
|
|
2577
2844
|
console.log("Design companion content cleared.");
|
|
2578
2845
|
} else {
|
|
2579
|
-
console.log("ijfw design -- Manage
|
|
2846
|
+
console.log("ijfw design -- Manage live preview and durable design intelligence.");
|
|
2580
2847
|
console.log("");
|
|
2581
|
-
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");
|
|
2582
2849
|
process.exit(1);
|
|
2583
2850
|
}
|
|
2584
2851
|
break;
|
|
@@ -2587,7 +2854,7 @@ async function main() {
|
|
|
2587
2854
|
const wantsBrowser = argv.slice(3).includes("--browser");
|
|
2588
2855
|
const candidates = [
|
|
2589
2856
|
join9(repoRoot(), "docs", "GUIDE.md"),
|
|
2590
|
-
|
|
2857
|
+
resolve4(__dirname2, "..", "docs", "GUIDE.md"),
|
|
2591
2858
|
join9(homedir(), ".ijfw", "docs", "GUIDE.md")
|
|
2592
2859
|
];
|
|
2593
2860
|
const guidePath = candidates.find((p) => existsSync4(p));
|
|
@@ -2605,7 +2872,7 @@ async function main() {
|
|
|
2605
2872
|
copyFileSync(join9(assetsSrc, f), join9(outDir, "assets", f));
|
|
2606
2873
|
}
|
|
2607
2874
|
}
|
|
2608
|
-
const md =
|
|
2875
|
+
const md = readFileSync4(guidePath, "utf8").replace(/\(guide\/assets\//g, "(assets/");
|
|
2609
2876
|
const rendered = marked.parse(md, { gfm: true, breaks: false });
|
|
2610
2877
|
const html = `<!doctype html>
|
|
2611
2878
|
<html lang="en"><head>
|
|
@@ -2624,7 +2891,7 @@ async function main() {
|
|
|
2624
2891
|
</head><body><div class="wrap markdown-body">${rendered}</div></body></html>`;
|
|
2625
2892
|
const outHtml = join9(outDir, "index.html");
|
|
2626
2893
|
writeFileSync4(outHtml, html);
|
|
2627
|
-
const opener =
|
|
2894
|
+
const opener = platform() === "darwin" ? "open" : platform() === "win32" ? "start" : "xdg-open";
|
|
2628
2895
|
spawnSync12(opener, [outHtml], { stdio: "ignore", detached: true });
|
|
2629
2896
|
console.log(`[ijfw] Guide opened in your browser.`);
|
|
2630
2897
|
console.log(` Local copy: ${outHtml}`);
|
|
@@ -2634,10 +2901,10 @@ async function main() {
|
|
|
2634
2901
|
if (hasLess) {
|
|
2635
2902
|
const lessRes = spawnSync12("less", ["-R", guidePath], { stdio: "inherit" });
|
|
2636
2903
|
if (lessRes.status !== 0 && lessRes.status !== null) {
|
|
2637
|
-
process.stdout.write(
|
|
2904
|
+
process.stdout.write(readFileSync4(guidePath, "utf8"));
|
|
2638
2905
|
}
|
|
2639
2906
|
} else {
|
|
2640
|
-
process.stdout.write(
|
|
2907
|
+
process.stdout.write(readFileSync4(guidePath, "utf8"));
|
|
2641
2908
|
}
|
|
2642
2909
|
process.exit(0);
|
|
2643
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 +
|
|
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) {
|
|
@@ -832,7 +848,7 @@ async function installGemini(ctx) {
|
|
|
832
848
|
for (const f of listFiles(agentSrc, ".md")) {
|
|
833
849
|
copyIfAbsent(f.path, join4(extDst, "agents", f.name));
|
|
834
850
|
}
|
|
835
|
-
ctx.log.ok("Installed Gemini bundle: MCP + extension +
|
|
851
|
+
ctx.log.ok("Installed Gemini bundle: MCP + extension + 19 skills + 11 hooks + policy");
|
|
836
852
|
return { status: "ok" };
|
|
837
853
|
}
|
|
838
854
|
async function installWayland(ctx) {
|
|
@@ -2048,14 +2064,12 @@ function cloneOrPull(dir, branch) {
|
|
|
2048
2064
|
else if (remoteSignal) console.warn(` git exited on signal ${remoteSignal}`);
|
|
2049
2065
|
else if (remoteStatus !== 0 && remoteStderr) console.warn(` git remote get-url: ${remoteStderr.slice(0, 120).trim()}`);
|
|
2050
2066
|
if (remoteStatus === 0) {
|
|
2051
|
-
const
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
"https://github.com/seandonahoe/ijfw/",
|
|
2055
|
-
"https://github.com/seandonahoe/ijfw.git/"
|
|
2067
|
+
const STALE_PATTERNS = [
|
|
2068
|
+
/^https:\/\/github\.com\/seandonahoe\/ijfw(\.git)?\/?$/i,
|
|
2069
|
+
/^https:\/\/github\.com\/therealseandonahoe\/ijfw(\.git)?\/?$/i
|
|
2056
2070
|
];
|
|
2057
2071
|
const currentOrigin = (stdout || "").trim();
|
|
2058
|
-
if (
|
|
2072
|
+
if (STALE_PATTERNS.some((re) => re.test(currentOrigin))) {
|
|
2059
2073
|
const setUrl = spawnSync2("git", ["-C", dir, "remote", "set-url", "origin", DEFAULT_REPO], { stdio: "inherit" });
|
|
2060
2074
|
if (setUrl.status !== 0) {
|
|
2061
2075
|
console.warn(` [!] origin migration failed -- could not repoint ${currentOrigin} to ${DEFAULT_REPO}`);
|
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 });
|
package/docs/GUIDE.md
CHANGED
|
@@ -172,7 +172,7 @@ Run `/team setup` in Claude Code, or `ijfw team` from the shell, to see your cur
|
|
|
172
172
|
|------|-------|--------------|
|
|
173
173
|
| Hot | Plain markdown in `.ijfw/memory/` | Always on. Instant reads. Git friendly. |
|
|
174
174
|
| Warm | BM25 ranked search | Always on. Scales to around 10,000 entries per project. |
|
|
175
|
-
| Cold | Optional semantic vectors |
|
|
175
|
+
| Cold | Optional semantic vectors | Off by default. Requires a user-installed embedding provider. |
|
|
176
176
|
|
|
177
177
|
Every session also ends with an optional "dream cycle". Run `/consolidate` or "run a dream cycle" to have IJFW sweep the day's memory: promote observed patterns into your knowledge base, prune stale entries, reconcile contradictions, optionally lift winners into global memory so every future project benefits. Memory that grows sharper over time instead of heavier.
|
|
178
178
|
|
|
@@ -359,9 +359,9 @@ IJFW configures six AI coding agents with native affordances on each, plus a uni
|
|
|
359
359
|
|
|
360
360
|
| Platform | What ships |
|
|
361
361
|
|----------|------------|
|
|
362
|
-
| Claude Code | Native plugin via marketplace, MCP auto-registered,
|
|
363
|
-
| Codex CLI | Native plugin (`.codex-plugin/plugin.json`),
|
|
364
|
-
| Gemini CLI | Native extension (`gemini-extension.json`),
|
|
362
|
+
| Claude Code | Native plugin via marketplace, MCP auto-registered, 6 hook events / 12 scripts, 22 on-demand skills, 22 slash commands |
|
|
363
|
+
| Codex CLI | Native plugin (`.codex-plugin/plugin.json`), 19 skills, 5 hook events, MCP registered, marketplace-ready |
|
|
364
|
+
| Gemini CLI | Native extension (`gemini-extension.json`), 19 skills, 11 hook events, 19 TOML slash commands, policy engine, BeforeModel injection, checkpointing |
|
|
365
365
|
| Cursor | `.cursor/mcp.json` plus `.cursor/rules/ijfw.mdc`. Dashboard view-only. |
|
|
366
366
|
| Windsurf | `~/.codeium/windsurf/mcp_config.json` plus `.windsurfrules`. Dashboard view-only. |
|
|
367
367
|
| Copilot (VS Code) | `.vscode/mcp.json` plus `.github/copilot-instructions.md`. Dashboard view-only. |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ijfw/install",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.2",
|
|
4
4
|
"description": "One-command installer for IJFW -- the AI efficiency layer. One install, every AI coding agent, zero config.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -55,7 +55,6 @@
|
|
|
55
55
|
"url": "git+https://gitlab.com/therealseandonahoe/ijfw.git"
|
|
56
56
|
},
|
|
57
57
|
"publishConfig": {
|
|
58
|
-
"access": "public"
|
|
59
|
-
"provenance": true
|
|
58
|
+
"access": "public"
|
|
60
59
|
}
|
|
61
60
|
}
|