javi-forge 1.6.1 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (159) hide show
  1. package/README.md +45 -1
  2. package/ci-local/README.md +56 -11
  3. package/ci-local/ci-local.ps1 +445 -0
  4. package/ci-local/ci-local.sh +52 -9
  5. package/ci-local/docker/python.Dockerfile +7 -0
  6. package/ci-local/hooks/commit-msg +120 -44
  7. package/ci-local/hooks/commit-msg.test.sh +286 -0
  8. package/ci-local/hooks/pre-commit +9 -1
  9. package/ci-local/hooks/pre-push +13 -2
  10. package/ci-local/install.ps1 +367 -0
  11. package/ci-local/install.sh +151 -5
  12. package/ci-local/uninstall.ps1 +85 -0
  13. package/ci-local/uninstall.sh +95 -0
  14. package/dist/cli/dispatch/ci.d.ts +12 -0
  15. package/dist/cli/dispatch/ci.js +38 -0
  16. package/dist/cli/dispatch/security.d.ts +11 -0
  17. package/dist/cli/dispatch/security.js +68 -0
  18. package/dist/cli/dispatch/simple-renderers.d.ts +16 -0
  19. package/dist/cli/dispatch/simple-renderers.js +65 -0
  20. package/dist/cli/dispatch/skill-publish.d.ts +15 -0
  21. package/dist/cli/dispatch/skill-publish.js +44 -0
  22. package/dist/cli/dispatch/skills-cmd.d.ts +13 -0
  23. package/dist/cli/dispatch/skills-cmd.js +93 -0
  24. package/dist/cli/dispatch/tdd.d.ts +11 -0
  25. package/dist/cli/dispatch/tdd.js +45 -0
  26. package/dist/cli/dispatch/types.d.ts +29 -0
  27. package/dist/cli/dispatch/types.js +2 -0
  28. package/dist/cli/dispatch/workflow.d.ts +11 -0
  29. package/dist/cli/dispatch/workflow.js +67 -0
  30. package/dist/cli/help.d.ts +127 -0
  31. package/dist/cli/help.js +138 -0
  32. package/dist/cli/runtime.d.ts +16 -0
  33. package/dist/cli/runtime.js +38 -0
  34. package/dist/cli/validators.d.ts +17 -0
  35. package/dist/cli/validators.js +35 -0
  36. package/dist/commands/analyze.js +1 -3
  37. package/dist/commands/ci.d.ts +47 -0
  38. package/dist/commands/ci.js +380 -63
  39. package/dist/commands/doctor.js +2 -4
  40. package/dist/commands/init/report.d.ts +13 -0
  41. package/dist/commands/init/report.js +13 -0
  42. package/dist/commands/init/steps/agent-skills.d.ts +13 -0
  43. package/dist/commands/init/steps/agent-skills.js +45 -0
  44. package/dist/commands/init/steps/ai-sync.d.ts +15 -0
  45. package/dist/commands/init/steps/ai-sync.js +62 -0
  46. package/dist/commands/init/steps/ci.d.ts +24 -0
  47. package/dist/commands/init/steps/ci.js +74 -0
  48. package/dist/commands/init/steps/claude-md.d.ts +14 -0
  49. package/dist/commands/init/steps/claude-md.js +54 -0
  50. package/dist/commands/init/steps/code-graph.d.ts +16 -0
  51. package/dist/commands/init/steps/code-graph.js +68 -0
  52. package/dist/commands/init/steps/context-dir.d.ts +13 -0
  53. package/dist/commands/init/steps/context-dir.js +47 -0
  54. package/dist/commands/init/steps/docker-deploy.d.ts +16 -0
  55. package/dist/commands/init/steps/docker-deploy.js +60 -0
  56. package/dist/commands/init/steps/ghagga.d.ts +15 -0
  57. package/dist/commands/init/steps/ghagga.js +58 -0
  58. package/dist/commands/init/steps/git.d.ts +25 -0
  59. package/dist/commands/init/steps/git.js +81 -0
  60. package/dist/commands/init/steps/gitignore.d.ts +13 -0
  61. package/dist/commands/init/steps/gitignore.js +39 -0
  62. package/dist/commands/init/steps/local-ai.d.ts +14 -0
  63. package/dist/commands/init/steps/local-ai.js +53 -0
  64. package/dist/commands/init/steps/manifest.d.ts +13 -0
  65. package/dist/commands/init/steps/manifest.js +58 -0
  66. package/dist/commands/init/steps/memory.d.ts +15 -0
  67. package/dist/commands/init/steps/memory.js +60 -0
  68. package/dist/commands/init/steps/mock.d.ts +13 -0
  69. package/dist/commands/init/steps/mock.js +64 -0
  70. package/dist/commands/init/steps/sdd.d.ts +12 -0
  71. package/dist/commands/init/steps/sdd.js +40 -0
  72. package/dist/commands/init/steps/security.d.ts +28 -0
  73. package/dist/commands/init/steps/security.js +103 -0
  74. package/dist/commands/init/types.d.ts +30 -0
  75. package/dist/commands/init/types.js +2 -0
  76. package/dist/commands/init.d.ts +5 -4
  77. package/dist/commands/init.js +42 -630
  78. package/dist/commands/llmstxt.js +1 -1
  79. package/dist/commands/plugin.js +1 -1
  80. package/dist/commands/security.js +23 -4
  81. package/dist/commands/skills/analysis.d.ts +19 -0
  82. package/dist/commands/skills/analysis.js +211 -0
  83. package/dist/commands/skills/benchmark.d.ts +6 -0
  84. package/dist/commands/skills/benchmark.js +82 -0
  85. package/dist/commands/skills/constants.d.ts +10 -0
  86. package/dist/commands/skills/constants.js +27 -0
  87. package/dist/commands/skills/directives.d.ts +21 -0
  88. package/dist/commands/skills/directives.js +101 -0
  89. package/dist/commands/skills/parsing.d.ts +16 -0
  90. package/dist/commands/skills/parsing.js +108 -0
  91. package/dist/commands/skills/rules.d.ts +5 -0
  92. package/dist/commands/skills/rules.js +32 -0
  93. package/dist/commands/skills/scoring.d.ts +47 -0
  94. package/dist/commands/skills/scoring.js +306 -0
  95. package/dist/commands/skills.d.ts +7 -125
  96. package/dist/commands/skills.js +8 -874
  97. package/dist/commands/tdd-pipeline.js +1 -1
  98. package/dist/commands/tdd.js +1 -1
  99. package/dist/commands/workflow.js +2 -0
  100. package/dist/constants.js +2 -2
  101. package/dist/index.js +26 -473
  102. package/dist/lib/agent-skills.js +1 -1
  103. package/dist/{commands → lib}/atlassian-mcp.js +2 -2
  104. package/dist/lib/auto-skill-install.js +2 -3
  105. package/dist/lib/auto-wire.js +7 -7
  106. package/dist/lib/ci-config.d.ts +65 -0
  107. package/dist/lib/ci-config.js +297 -0
  108. package/dist/lib/claudemd.js +1 -1
  109. package/dist/lib/codex-export.js +4 -8
  110. package/dist/lib/common.js +2 -2
  111. package/dist/lib/context.js +2 -2
  112. package/dist/{commands → lib}/crash-recovery.js +4 -4
  113. package/dist/lib/docker.d.ts +22 -1
  114. package/dist/lib/docker.js +57 -49
  115. package/dist/lib/exec.d.ts +10 -0
  116. package/dist/lib/exec.js +11 -0
  117. package/dist/lib/frontmatter.js +2 -2
  118. package/dist/{commands → lib}/harness-audit.js +1 -1
  119. package/dist/{commands → lib}/parallel-batch.js +1 -3
  120. package/dist/lib/plugin.js +2 -4
  121. package/dist/{commands → lib}/secret-scanner.js +3 -1
  122. package/dist/{commands → lib}/security-analysis.js +4 -2
  123. package/dist/lib/skill-publish.js +23 -5
  124. package/dist/{commands → lib}/skill-scanner.d.ts +1 -1
  125. package/dist/{commands → lib}/skill-scanner.js +3 -3
  126. package/dist/lib/stack-detector.js +3 -3
  127. package/dist/{commands → lib}/supply-chain.js +1 -1
  128. package/dist/lib/template.js +2 -2
  129. package/dist/lib/workflow/discovery.d.ts +8 -0
  130. package/dist/lib/workflow/discovery.js +18 -1
  131. package/dist/lib/workflow/parser.js +15 -15
  132. package/dist/lib/workflow/validator.js +4 -4
  133. package/dist/tasks/index.d.ts +3 -3
  134. package/dist/tasks/index.js +3 -3
  135. package/dist/tasks/scaffold-tasks.d.ts +1 -1
  136. package/dist/tasks/scaffold-tasks.js +10 -10
  137. package/dist/tasks/task-id.d.ts +1 -1
  138. package/dist/tasks/task-id.js +9 -5
  139. package/dist/tasks/task-tracker.d.ts +1 -1
  140. package/dist/tasks/task-tracker.js +7 -7
  141. package/dist/ui/App.js +1 -1
  142. package/dist/ui/CIContext.js +1 -2
  143. package/dist/ui/Header.js +4 -4
  144. package/dist/ui/NameInput.js +1 -1
  145. package/dist/ui/Progress.d.ts +1 -1
  146. package/dist/ui/Progress.js +1 -1
  147. package/dist/ui/StackSelector.js +1 -1
  148. package/lib/common.psm1 +167 -0
  149. package/lib/common.sh +30 -0
  150. package/package.json +38 -19
  151. /package/dist/{commands → lib}/atlassian-mcp.d.ts +0 -0
  152. /package/dist/{commands → lib}/crash-recovery.d.ts +0 -0
  153. /package/dist/{commands → lib}/harness-audit.d.ts +0 -0
  154. /package/dist/{commands → lib}/parallel-batch.d.ts +0 -0
  155. /package/dist/{commands → lib}/secret-scanner.d.ts +0 -0
  156. /package/dist/{commands → lib}/security-analysis.d.ts +0 -0
  157. /package/dist/{commands → lib}/supply-chain.d.ts +0 -0
  158. /package/dist/{commands → lib}/team-presets.d.ts +0 -0
  159. /package/dist/{commands → lib}/team-presets.js +0 -0
@@ -1,5 +1,5 @@
1
+ import path from "node:path";
1
2
  import fs from "fs-extra";
2
- import path from "path";
3
3
  import { detectCIStack } from "./ci.js";
4
4
  import { getTddTestCommand } from "./tdd.js";
5
5
  // =============================================================================
@@ -1,5 +1,5 @@
1
+ import path from "node:path";
1
2
  import fs from "fs-extra";
2
- import path from "path";
3
3
  import { detectCIStack } from "./ci.js";
4
4
  // =============================================================================
5
5
  // Test command resolution
@@ -46,6 +46,8 @@ async function resolveGraph(target, projectDir, template, onStep) {
46
46
  return null;
47
47
  }
48
48
  const first = discovered[0];
49
+ if (!first)
50
+ return null;
49
51
  const content = await fs.readFile(first.path, "utf-8");
50
52
  const parser = first.format === "dot" ? parseDot : parseMermaid;
51
53
  const graph = parser(content, first.name);
package/dist/constants.js CHANGED
@@ -1,5 +1,5 @@
1
- import path from "path";
2
- import { fileURLToPath } from "url";
1
+ import path from "node:path";
2
+ import { fileURLToPath } from "node:url";
3
3
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
4
4
  /** Root of the javi-forge package (one level up from dist/) */
5
5
  export const FORGE_ROOT = path.resolve(__dirname, "..");
package/dist/index.js CHANGED
@@ -1,516 +1,69 @@
1
1
  #!/usr/bin/env node
2
- import { PassThrough } from "node:stream";
3
- import { render } from "ink";
2
+ import { createRequire } from "node:module";
4
3
  import meow from "meow";
5
- import { createRequire } from "module";
6
- import path from "path";
7
- import React from "react";
8
- import updateNotifier from "update-notifier";
9
- import AnalyzeUI from "./ui/AnalyzeUI.js";
10
- import App from "./ui/App.js";
11
- import AutoSkills from "./ui/AutoSkills.js";
12
- import CI from "./ui/CI.js";
13
- import { CIProvider as CIContextProvider } from "./ui/CIContext.js";
14
- import Doctor from "./ui/Doctor.js";
15
- import LlmsTxt from "./ui/LlmsTxt.js";
16
- import Plugin from "./ui/Plugin.js";
17
- import Skills from "./ui/Skills.js";
4
+ import { handleCi } from "./cli/dispatch/ci.js";
5
+ import { handleSecurity } from "./cli/dispatch/security.js";
6
+ import { handleAnalyze, handleDoctor, handleInitDefault, handleLlmsTxt, handlePlugin, } from "./cli/dispatch/simple-renderers.js";
7
+ import { handleSkillPublish } from "./cli/dispatch/skill-publish.js";
8
+ import { handleSkillsCmd } from "./cli/dispatch/skills-cmd.js";
9
+ import { handleTdd } from "./cli/dispatch/tdd.js";
10
+ import { handleWorkflow } from "./cli/dispatch/workflow.js";
11
+ import { FLAGS_SCHEMA, HELP_TEXT } from "./cli/help.js";
12
+ import { createInkStdin, detectCI, setupUpdateNotifier, } from "./cli/runtime.js";
18
13
  // Check for updates in background (non-blocking, cached 24h)
19
14
  const _require = createRequire(import.meta.url);
20
15
  const pkg = _require("../package.json");
21
- updateNotifier({ pkg, updateCheckInterval: 1000 * 60 * 60 * 24 }).notify();
22
- const cli = meow(`
23
- Usage
24
- $ javi-forge [command] [options]
25
-
26
- Commands
27
- init Bootstrap a new project (default)
28
- ci Run CI simulation (lint + compile + test + security + ghagga)
29
- tdd init Install TDD-enforcing pre-commit hook (auto-detects stack)
30
- tdd pipeline Install TDD pipeline pre-push hook (--mode strict|warn)
31
- analyze Run repoforge skills analysis
32
- doctor Show health report
33
- workflow show Render a workflow graph as ASCII (--template <name> or file path)
34
- workflow validate Validate project state against a workflow graph
35
- workflow list List available workflows and built-in templates
36
- plugin add Install a plugin from GitHub (org/repo)
37
- plugin remove Remove an installed plugin
38
- plugin list List installed plugins
39
- plugin search Search the plugin registry
40
- plugin validate Validate a local plugin directory
41
- plugin sync Auto-detect and wire installed plugins
42
- plugin export Export plugin to Agent Skills spec format (skills.json)
43
- plugin export --codex: Export plugin to Codex-compatible TOML subagent files
44
- plugin export-skills Generate aggregated skills.json from all installed plugins
45
- plugin export-skills global Generate global skills.json from all globally installed plugins
46
- plugin import Import an Agent Skills spec package as a javi-forge plugin
47
- skills doctor Show skills health report (add --deep for conflict detection)
48
- skills budget Show token cost of loaded skills (add -b N for custom budget)
49
- skills score Score a skill on quality dimensions (completeness, clarity, testability, token-efficiency)
50
- skills benchmark Benchmark a skill with structural quality checks
51
- skills auto Auto-detect project stack and suggest/install matching AI skills
52
- skills auto-install Alias for skills auto
53
- skill publish Package a skill directory for marketplace distribution (generates plugin.json)
54
- security baseline Create security baseline from current audit findings
55
- security check Check for regressions against baseline (exits non-zero if found)
56
- security update Re-snapshot baseline (acknowledge current vulns)
57
- security allowlist Add all current findings to the allowlist (suppress in future checks)
58
- llms-txt Generate AI-friendly llms.txt for current project
59
-
60
- Options
61
- --dry-run Preview changes without writing files
62
- --stack Project stack (node, python, go, rust, java-gradle, java-maven, elixir)
63
- --ci CI provider (github, gitlab, woodpecker)
64
- --memory Memory module (engram, obsidian-brain, memory-simple, none)
65
- --project-name Project name (skips name prompt)
66
- --ghagga Enable GHAGGA review system
67
- --mock Enable mock-first mode (no real API keys needed)
68
- --local-ai Include local AI dev stack (Ollama + Docker Compose)
69
- --batch Non-interactive mode (auto-proceed, no keyboard input)
70
- --deep Enable deep analysis (conflict + duplicate detection)
71
- --budget, -b Token budget limit for skills (default: 8000)
72
- --skills-dir Custom skills directory path
73
- --author Author name for skill publish
74
- --repo Repository URL for skill publish
75
- --version Show version
76
- --help Show this help
77
-
78
- CI options (javi-forge ci)
79
- --quick Lint + compile only (fast, for pre-commit)
80
- --shell Open interactive shell in CI container
81
- --detect Show detected stack and exit
82
- --no-docker Run commands natively (no Docker)
83
- --no-ci-ghagga Skip GHAGGA review
84
- --no-security Skip Semgrep security scan
85
- --timeout N Per-step timeout in seconds (default: 600)
86
-
87
- CI hooks (javi-forge ci init)
88
- Install git hooks that call javi-forge ci.
89
- No files copied — hooks reference the global CLI.
90
-
91
- Examples
92
- $ javi-forge
93
- $ javi-forge init --dry-run
94
- $ javi-forge init --stack node --ci github
95
- $ javi-forge ci
96
- $ javi-forge ci init
97
- $ javi-forge tdd init
98
- $ javi-forge ci --quick
99
- $ javi-forge ci --no-ci-ghagga --no-security
100
- $ javi-forge ci --no-docker
101
- $ javi-forge ci --shell
102
- $ javi-forge analyze
103
- $ javi-forge doctor
104
- $ javi-forge plugin add mapbox/agent-skills
105
- $ javi-forge plugin list
106
- `, {
16
+ setupUpdateNotifier(pkg);
17
+ const cli = meow(HELP_TEXT, {
107
18
  importMeta: import.meta,
108
- flags: {
109
- dryRun: { type: "boolean", default: false },
110
- stack: { type: "string", default: "" },
111
- ci: { type: "string", default: "" },
112
- memory: { type: "string", default: "" },
113
- projectName: { type: "string", default: "" },
114
- ghagga: { type: "boolean", default: false },
115
- mock: { type: "boolean", default: false },
116
- localAi: { type: "boolean", default: false },
117
- batch: { type: "boolean", default: false },
118
- // CI flags
119
- quick: { type: "boolean", default: false },
120
- shell: { type: "boolean", default: false },
121
- detect: { type: "boolean", default: false },
122
- docker: { type: "boolean", default: true },
123
- ciGhagga: { type: "boolean", default: true },
124
- security: { type: "boolean", default: true },
125
- timeout: { type: "number", default: 600 },
126
- // Security check flags
127
- minSeverity: { type: "string", default: "low" },
128
- staleDays: { type: "number", default: 30 },
129
- json: { type: "boolean", default: false },
130
- // Plugin flags
131
- codex: { type: "boolean", default: false },
132
- // Skills flags
133
- deep: { type: "boolean", default: false },
134
- budget: { type: "number", shortFlag: "b", default: 8000 },
135
- skillsDir: { type: "string", default: "" },
136
- // Skill publish flags
137
- author: { type: "string", default: "" },
138
- repo: { type: "string", default: "" },
139
- // Workflow flags
140
- template: { type: "string", default: "" },
141
- },
19
+ flags: FLAGS_SCHEMA,
142
20
  });
143
21
  const subcommand = cli.input[0] ?? "init";
144
- const VALID_STACKS = [
145
- "node",
146
- "python",
147
- "go",
148
- "rust",
149
- "java-gradle",
150
- "java-maven",
151
- "elixir",
152
- ];
153
- const VALID_CI = ["github", "gitlab", "woodpecker"];
154
- const VALID_MEMORY = ["engram", "obsidian-brain", "memory-simple", "none"];
155
- const isCI = cli.flags.batch || process.env["CI"] === "1" || process.env["CI"] === "true";
156
- // When stdin doesn't support raw mode (pipes, subprocesses, CI), provide a fake
157
- // stdin stream so Ink doesn't crash trying to enable raw mode on a non-TTY pipe.
158
- const isTTY = process.stdin.isTTY === true;
159
- const fakeStdin = new PassThrough();
160
- Object.defineProperty(fakeStdin, "isTTY", { value: false });
161
- const inkStdin = isTTY ? process.stdin : fakeStdin;
22
+ const isCI = detectCI(cli.flags);
23
+ const inkStdin = createInkStdin();
162
24
  switch (subcommand) {
163
25
  case "tdd": {
164
- if (cli.input[1] === "init") {
165
- const { installTddHooks } = await import("./commands/tdd.js");
166
- const { installed, errors } = await installTddHooks(process.cwd());
167
- if (installed.length > 0) {
168
- console.log(`\u2713 Installed TDD hooks: ${installed.join(", ")}`);
169
- console.log(" Pre-commit hook enforces tests must pass before commit");
170
- }
171
- for (const err of errors) {
172
- console.error(`\u2717 ${err}`);
173
- }
174
- process.exit(errors.length > 0 ? 1 : 0);
175
- }
176
- else if (cli.input[1] === "pipeline") {
177
- const { installTddPipelineHook } = await import("./commands/tdd-pipeline.js");
178
- const mode = cli.flags.mode === "warn"
179
- ? "warn"
180
- : "strict";
181
- const result = await installTddPipelineHook(process.cwd(), mode);
182
- if (result.installed.length > 0) {
183
- console.log(`\u2713 Installed TDD pipeline hook: ${result.installed.join(", ")} [${result.mode}]`);
184
- console.log(` Pre-push hook enforces TDD pipeline (${result.mode} mode)`);
185
- }
186
- for (const skip of result.skipped) {
187
- console.log(`\u26A0 ${skip}`);
188
- }
189
- for (const err of result.errors) {
190
- console.error(`\u2717 ${err}`);
191
- }
192
- process.exit(result.errors.length > 0 ? 1 : 0);
193
- }
194
- else {
195
- console.error("Usage: javi-forge tdd <command>");
196
- console.error(" init Install TDD-enforcing pre-commit hook");
197
- console.error(" pipeline Install TDD pipeline pre-push hook (--mode strict|warn)");
198
- process.exit(1);
199
- }
26
+ await handleTdd(cli);
200
27
  break;
201
28
  }
202
29
  case "ci": {
203
- // Sub-command: javi-forge ci init → install git hooks
204
- if (cli.input[1] === "init") {
205
- const { installCIHooks } = await import("./commands/ci.js");
206
- const { installed, errors } = await installCIHooks(process.cwd());
207
- if (installed.length > 0) {
208
- console.log(`✓ Installed git hooks: ${installed.join(", ")}`);
209
- console.log(" Hooks call javi-forge ci (with npx fallback)");
210
- }
211
- for (const err of errors) {
212
- console.error(`✗ ${err}`);
213
- }
214
- process.exit(errors.length > 0 ? 1 : 0);
215
- break;
216
- }
217
- const ciMode = cli.flags.detect
218
- ? "detect"
219
- : cli.flags.shell
220
- ? "shell"
221
- : cli.flags.quick
222
- ? "quick"
223
- : "full";
224
- render(React.createElement(CIContextProvider, { isCI: true },
225
- React.createElement(CI, { projectDir: process.cwd(), mode: ciMode, noDocker: !cli.flags.docker, noGhagga: !cli.flags.ciGhagga, noSecurity: !cli.flags.security, timeout: cli.flags.timeout })), { stdin: inkStdin });
30
+ await handleCi(cli, { inkStdin, isCI });
226
31
  break;
227
32
  }
228
33
  case "doctor": {
229
- render(React.createElement(CIContextProvider, { isCI: isCI },
230
- React.createElement(Doctor, null)), { stdin: inkStdin });
34
+ handleDoctor(cli, { inkStdin, isCI });
231
35
  break;
232
36
  }
233
37
  case "analyze": {
234
- render(React.createElement(CIContextProvider, { isCI: isCI },
235
- React.createElement(AnalyzeUI, { dryRun: cli.flags.dryRun })), { stdin: inkStdin });
38
+ handleAnalyze(cli, { inkStdin, isCI });
236
39
  break;
237
40
  }
238
41
  case "workflow": {
239
- const workflowAction = cli.input[1];
240
- const VALID_WORKFLOW_ACTIONS = ["show", "validate", "list"];
241
- if (!workflowAction || !VALID_WORKFLOW_ACTIONS.includes(workflowAction)) {
242
- console.error("Usage: javi-forge workflow <show|validate|list>");
243
- console.error(" show Render a workflow graph as ASCII");
244
- console.error(" validate Validate project state against a workflow graph");
245
- console.error(" list List available workflows and built-in templates");
246
- console.error("");
247
- console.error(" Options:");
248
- console.error(" --template <name> Use a built-in template (ci-pipeline, release, feature-flow)");
249
- console.error(" <file> Path to a .dot or .mermaid file");
250
- process.exit(1);
251
- break;
252
- }
253
- const { runWorkflowShow, runWorkflowValidate, runWorkflowList } = await import("./commands/workflow.js");
254
- const workflowTarget = cli.input[2];
255
- const workflowTemplate = cli.flags.template || undefined;
256
- const onStep = (step) => {
257
- const icon = step.status === "done"
258
- ? "\u2713"
259
- : step.status === "error"
260
- ? "\u2717"
261
- : "\u25CB";
262
- console.log(`${icon} ${step.label}`);
263
- if (step.detail)
264
- console.log(` ${step.detail}`);
265
- };
266
- try {
267
- if (workflowAction === "show") {
268
- const output = await runWorkflowShow(process.cwd(), onStep, {
269
- target: workflowTarget,
270
- template: workflowTemplate,
271
- });
272
- if (output)
273
- console.log("\n" + output);
274
- else
275
- process.exit(1);
276
- }
277
- else if (workflowAction === "validate") {
278
- const output = await runWorkflowValidate(process.cwd(), onStep, {
279
- target: workflowTarget,
280
- template: workflowTemplate,
281
- });
282
- if (output)
283
- console.log("\n" + output);
284
- else
285
- process.exit(1);
286
- }
287
- else {
288
- const output = await runWorkflowList(process.cwd(), onStep);
289
- console.log("\n" + output);
290
- }
291
- }
292
- catch (e) {
293
- console.error(`\u2717 ${e instanceof Error ? e.message : String(e)}`);
294
- process.exit(1);
295
- }
42
+ await handleWorkflow(cli);
296
43
  break;
297
44
  }
298
45
  case "llms-txt": {
299
- render(React.createElement(CIContextProvider, { isCI: isCI },
300
- React.createElement(LlmsTxt, { projectDir: process.cwd(), dryRun: cli.flags.dryRun })), { stdin: inkStdin });
46
+ handleLlmsTxt(cli, { inkStdin, isCI });
301
47
  break;
302
48
  }
303
49
  case "plugin": {
304
- const pluginAction = cli.input[1];
305
- const VALID_PLUGIN_ACTIONS = [
306
- "add",
307
- "remove",
308
- "list",
309
- "search",
310
- "validate",
311
- "sync",
312
- "export",
313
- "import",
314
- "export-skills",
315
- ];
316
- const action = pluginAction && VALID_PLUGIN_ACTIONS.includes(pluginAction)
317
- ? pluginAction
318
- : "list";
319
- const target = cli.input[2];
320
- render(React.createElement(CIContextProvider, { isCI: isCI },
321
- React.createElement(Plugin, { action: action, target: target, dryRun: cli.flags.dryRun, codex: cli.flags.codex })), { stdin: inkStdin });
50
+ handlePlugin(cli, { inkStdin, isCI });
322
51
  break;
323
52
  }
324
53
  case "skills": {
325
- const skillsAction = cli.input[1];
326
- const VALID_SKILLS_ACTIONS = [
327
- "doctor",
328
- "budget",
329
- "score",
330
- "benchmark",
331
- "auto",
332
- "auto-install",
333
- ];
334
- if (!skillsAction || !VALID_SKILLS_ACTIONS.includes(skillsAction)) {
335
- console.error("Usage: javi-forge skills <doctor|budget|score|benchmark|auto>");
336
- console.error(" doctor Show skills health report (add --deep for conflict detection)");
337
- console.error(" budget Show token cost of loaded skills (add -b N for custom budget)");
338
- console.error(" score Score a skill on quality dimensions (0-100)");
339
- console.error(" benchmark Benchmark a skill with structural quality checks");
340
- console.error(" auto Auto-detect project stack and suggest/install matching AI skills");
341
- console.error(" auto-install Alias for auto");
342
- process.exit(1);
343
- break;
344
- }
345
- // Auto / auto-install: render interactive Ink UI
346
- if (skillsAction === "auto" || skillsAction === "auto-install") {
347
- render(React.createElement(CIContextProvider, { isCI: isCI },
348
- React.createElement(AutoSkills, { projectDir: process.cwd(), skillsDir: cli.flags.skillsDir || undefined, dryRun: cli.flags.dryRun })), { stdin: inkStdin });
349
- break;
350
- }
351
- // Score and benchmark are non-interactive CLI commands
352
- if (skillsAction === "score" || skillsAction === "benchmark") {
353
- const targetSkill = cli.input[2];
354
- if (!targetSkill) {
355
- console.error(`Usage: javi-forge skills ${skillsAction} <skill-name>`);
356
- process.exit(1);
357
- break;
358
- }
359
- const skillsDir = cli.flags.skillsDir ||
360
- path.join(process.env["HOME"] ?? "~", ".claude", "skills");
361
- const skillPath = path.join(skillsDir, targetSkill, "SKILL.md");
362
- if (skillsAction === "score") {
363
- const { scoreSkill } = await import("./commands/skills.js");
364
- const result = await scoreSkill(skillPath, cli.flags.budget);
365
- if (!result) {
366
- console.error(`\u2717 Skill not found: ${skillPath}`);
367
- process.exit(1);
368
- break;
369
- }
370
- console.log(`\nSkill: ${result.skillName}`);
371
- console.log(` Completeness: ${result.completeness}/100`);
372
- console.log(` Clarity: ${result.clarity}/100`);
373
- console.log(` Testability: ${result.testability}/100`);
374
- console.log(` Token Efficiency: ${result.tokenEfficiency}/100`);
375
- console.log(` Safety: ${result.safety}/100`);
376
- console.log(` Agent Readiness: ${result.agentReadiness}/100`);
377
- console.log(` ─────────────────────────`);
378
- console.log(` Overall: ${result.overall}/100`);
379
- console.log(` Grade: ${result.grade}`);
380
- console.log(` Threshold: ${result.threshold}`);
381
- console.log(` Status: ${result.passing ? "\u2713 PASSING" : "\u2717 FAILING"}`);
382
- process.exit(result.passing ? 0 : 1);
383
- }
384
- else {
385
- const { benchmarkSkill } = await import("./commands/skills.js");
386
- const result = await benchmarkSkill(skillPath);
387
- if (!result) {
388
- console.error(`\u2717 Skill not found: ${skillPath}`);
389
- process.exit(1);
390
- break;
391
- }
392
- console.log(`\nBenchmark: ${result.skillName}`);
393
- for (const check of result.checks) {
394
- const icon = check.passed ? "\u2713" : "\u2717";
395
- console.log(` ${icon} ${check.name}${check.detail ? ` — ${check.detail}` : ""}`);
396
- }
397
- console.log(`\n Pass rate: ${result.passRate}%`);
398
- process.exit(result.passRate >= 50 ? 0 : 1);
399
- }
400
- break;
401
- }
402
- const skillsMode = skillsAction;
403
- render(React.createElement(CIContextProvider, { isCI: isCI },
404
- React.createElement(Skills, { mode: skillsMode, budget: cli.flags.budget, deep: cli.flags.deep, skillsDir: cli.flags.skillsDir || undefined })), { stdin: inkStdin });
54
+ await handleSkillsCmd(cli, { inkStdin, isCI });
405
55
  break;
406
56
  }
407
57
  case "skill": {
408
- const skillAction = cli.input[1];
409
- if (skillAction !== "publish") {
410
- console.error("Usage: javi-forge skill <publish>");
411
- console.error(" publish Package a skill directory for marketplace distribution");
412
- process.exit(1);
413
- break;
414
- }
415
- const targetDir = cli.input[2] ?? process.cwd();
416
- const { publishSkill } = await import("./lib/skill-publish.js");
417
- const result = await publishSkill({
418
- skillDir: path.resolve(targetDir),
419
- author: cli.flags.author || undefined,
420
- repository: cli.flags.repo || undefined,
421
- dryRun: cli.flags.dryRun,
422
- });
423
- if (result.success) {
424
- console.log(`\u2713 Published: ${result.manifest?.name}@${result.manifest?.version}`);
425
- console.log(` plugin.json: ${result.pluginJsonPath}`);
426
- if (result.manifest?.tags?.length) {
427
- console.log(` tags: ${result.manifest.tags.join(", ")}`);
428
- }
429
- if (cli.flags.dryRun) {
430
- console.log(" (dry-run: no files written)");
431
- }
432
- }
433
- else {
434
- console.error(`\u2717 ${result.error}`);
435
- process.exit(1);
436
- }
58
+ await handleSkillPublish(cli);
437
59
  break;
438
60
  }
439
61
  case "security": {
440
- const securityAction = cli.input[1];
441
- const VALID_SECURITY_ACTIONS = ["baseline", "check", "update", "allowlist"];
442
- if (!securityAction || !VALID_SECURITY_ACTIONS.includes(securityAction)) {
443
- console.error("Usage: javi-forge security <baseline|check|update|allowlist>");
444
- console.error(" baseline Create security baseline from current audit findings");
445
- console.error(" check Check for regressions against baseline (exits non-zero if found)");
446
- console.error(" update Re-snapshot baseline (acknowledge current vulns)");
447
- console.error(" allowlist Add all current findings to the allowlist (suppress in future checks)");
448
- console.error("");
449
- console.error(" Options (check mode):");
450
- console.error(" --min-severity <level> Only fail on regressions >= level (critical|high|moderate|low|info)");
451
- console.error(" --stale-days <N> Warn if baseline older than N days (default: 30)");
452
- console.error(" --json Output result as JSON (for CI integration)");
453
- process.exit(1);
454
- break;
455
- }
456
- const { runSecurity } = await import("./commands/security.js");
457
- const mode = securityAction;
458
- const rawMinSev = cli.flags["minSeverity"];
459
- const validSeverities = [
460
- "critical",
461
- "high",
462
- "moderate",
463
- "low",
464
- "info",
465
- ];
466
- const checkOptions = {
467
- minSeverity: (rawMinSev && validSeverities.includes(rawMinSev)
468
- ? rawMinSev
469
- : "low"),
470
- staleDays: cli.flags["staleDays"],
471
- };
472
- const jsonOutput = !!cli.flags["json"];
473
- try {
474
- const result = await runSecurity(mode, process.cwd(), (step) => {
475
- if (jsonOutput)
476
- return; // suppress step output in JSON mode
477
- const icon = step.status === "done"
478
- ? "\u2713"
479
- : step.status === "error"
480
- ? "\u2717"
481
- : step.status === "skipped"
482
- ? "-"
483
- : "\u25CB";
484
- console.log(`${icon} ${step.label}`);
485
- if (step.detail)
486
- console.log(` ${step.detail}`);
487
- }, checkOptions);
488
- if (jsonOutput && result) {
489
- console.log(JSON.stringify(result, null, 2));
490
- }
491
- if (mode === "check" && result && result.filteredRegressions.length > 0) {
492
- process.exit(1);
493
- }
494
- }
495
- catch {
496
- process.exit(1);
497
- }
62
+ await handleSecurity(cli);
498
63
  break;
499
64
  }
500
- case "init":
501
65
  default: {
502
- const presetStack = VALID_STACKS.includes(cli.flags.stack)
503
- ? cli.flags.stack
504
- : undefined;
505
- const presetCI = VALID_CI.includes(cli.flags.ci)
506
- ? cli.flags.ci
507
- : undefined;
508
- const presetMemory = VALID_MEMORY.includes(cli.flags.memory)
509
- ? cli.flags.memory
510
- : undefined;
511
- const presetName = cli.flags.projectName || undefined;
512
- render(React.createElement(CIContextProvider, { isCI: isCI },
513
- React.createElement(App, { dryRun: cli.flags.dryRun, presetStack: presetStack, presetCI: presetCI, presetMemory: presetMemory, presetName: presetName, presetGhagga: cli.flags.ghagga, presetMock: cli.flags.mock ?? false, presetLocalAi: cli.flags.localAi ?? false })), { stdin: inkStdin });
66
+ handleInitDefault(cli, { inkStdin, isCI });
514
67
  break;
515
68
  }
516
69
  }
@@ -1,5 +1,5 @@
1
+ import path from "node:path";
1
2
  import fs from "fs-extra";
2
- import path from "path";
3
3
  import { AGENT_SKILLS_MANIFEST_FILE, PLUGIN_MANIFEST_FILE, PLUGINS_DIR, } from "../constants.js";
4
4
  // ── Conversion ─────────────────────────────────────────────────────────────
5
5
  /**
@@ -5,9 +5,9 @@
5
5
  * Produces a config snippet that can be merged into .claude/settings.json
6
6
  * or any AI agent's MCP configuration.
7
7
  */
8
+ import path from "node:path";
9
+ import { fileURLToPath } from "node:url";
8
10
  import fs from "fs-extra";
9
- import path from "path";
10
- import { fileURLToPath } from "url";
11
11
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
12
12
  const TEMPLATES_DIR = path.resolve(__dirname, "../../templates");
13
13
  /**
@@ -1,8 +1,7 @@
1
+ import path from "node:path";
1
2
  import fs from "fs-extra";
2
- import path from "path";
3
+ import { DEFAULT_SKILLS_DIR } from "../commands/skills/constants.js";
3
4
  import { detectProjectStack } from "./stack-detector.js";
4
- // ── Constants ──────────────────────────────────────────────────────────────
5
- const DEFAULT_SKILLS_DIR = path.join(process.env["HOME"] ?? "~", ".claude", "skills");
6
5
  // ── Core ───────────────────────────────────────────────────────────────────
7
6
  /**
8
7
  * Scan a project, detect its stack, and install matching AI skills.
@@ -1,5 +1,5 @@
1
+ import path from "node:path";
1
2
  import fs from "fs-extra";
2
- import path from "path";
3
3
  // ── Markers ────────────────────────────────────────────────────────────────
4
4
  const WIRE_START = "<!-- javi-forge:auto-wire:start -->";
5
5
  const WIRE_END = "<!-- javi-forge:auto-wire:end -->";
@@ -136,7 +136,7 @@ async function wireClaudeMd(projectDir, desiredSkills, plugins, dryRun) {
136
136
  }
137
137
  // Build new section
138
138
  const section = buildAutoWireSection(pluginsWithCapabilities);
139
- const newContent = cleaned.trimEnd() + "\n\n" + section + "\n";
139
+ const newContent = `${cleaned.trimEnd()}\n\n${section}\n`;
140
140
  if (!dryRun) {
141
141
  await fs.ensureDir(path.dirname(claudeMdPath));
142
142
  await fs.writeFile(claudeMdPath, newContent, "utf-8");
@@ -182,7 +182,7 @@ async function wireSettingsJson(projectDir, desiredHooks, _plugins, dryRun) {
182
182
  }
183
183
  }
184
184
  // Ensure hooks object
185
- const hooks = (settings["hooks"] ?? {});
185
+ const hooks = (settings.hooks ?? {});
186
186
  const pluginHooks = (hooks["plugin-hooks"] ?? []);
187
187
  const existingSet = new Set(pluginHooks);
188
188
  for (const entry of desiredHooks) {
@@ -195,7 +195,7 @@ async function wireSettingsJson(projectDir, desiredHooks, _plugins, dryRun) {
195
195
  wired.push(entry);
196
196
  }
197
197
  // Detect hooks that belong to removed plugins
198
- const activePluginNames = new Set(desiredHooks.map((h) => h.plugin));
198
+ const _activePluginNames = new Set(desiredHooks.map((h) => h.plugin));
199
199
  const cleanedHooks = pluginHooks.filter((h) => {
200
200
  // Keep hooks that we just wired or that belong to active plugins
201
201
  const belongsToActive = desiredHooks.some((d) => d.value === h);
@@ -211,7 +211,7 @@ async function wireSettingsJson(projectDir, desiredHooks, _plugins, dryRun) {
211
211
  return true;
212
212
  });
213
213
  hooks["plugin-hooks"] = cleanedHooks;
214
- settings["hooks"] = hooks;
214
+ settings.hooks = hooks;
215
215
  if (!dryRun && (wired.length > 0 || unwired.length > 0)) {
216
216
  await fs.ensureDir(path.dirname(settingsPath));
217
217
  await fs.writeJson(settingsPath, settings, { spaces: 2 });
@@ -233,8 +233,8 @@ export function removeAutoWireSection(content) {
233
233
  const before = content.slice(0, startIdx).trimEnd();
234
234
  const after = content.slice(endIdx + WIRE_END.length).trimStart();
235
235
  if (after) {
236
- return before + "\n\n" + after;
236
+ return `${before}\n\n${after}`;
237
237
  }
238
- return before + "\n";
238
+ return `${before}\n`;
239
239
  }
240
240
  //# sourceMappingURL=auto-wire.js.map