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.
- package/README.md +45 -1
- package/ci-local/README.md +56 -11
- package/ci-local/ci-local.ps1 +445 -0
- package/ci-local/ci-local.sh +52 -9
- package/ci-local/docker/python.Dockerfile +7 -0
- package/ci-local/hooks/commit-msg +120 -44
- package/ci-local/hooks/commit-msg.test.sh +286 -0
- package/ci-local/hooks/pre-commit +9 -1
- package/ci-local/hooks/pre-push +13 -2
- package/ci-local/install.ps1 +367 -0
- package/ci-local/install.sh +151 -5
- package/ci-local/uninstall.ps1 +85 -0
- package/ci-local/uninstall.sh +95 -0
- package/dist/cli/dispatch/ci.d.ts +12 -0
- package/dist/cli/dispatch/ci.js +38 -0
- package/dist/cli/dispatch/security.d.ts +11 -0
- package/dist/cli/dispatch/security.js +68 -0
- package/dist/cli/dispatch/simple-renderers.d.ts +16 -0
- package/dist/cli/dispatch/simple-renderers.js +65 -0
- package/dist/cli/dispatch/skill-publish.d.ts +15 -0
- package/dist/cli/dispatch/skill-publish.js +44 -0
- package/dist/cli/dispatch/skills-cmd.d.ts +13 -0
- package/dist/cli/dispatch/skills-cmd.js +93 -0
- package/dist/cli/dispatch/tdd.d.ts +11 -0
- package/dist/cli/dispatch/tdd.js +45 -0
- package/dist/cli/dispatch/types.d.ts +29 -0
- package/dist/cli/dispatch/types.js +2 -0
- package/dist/cli/dispatch/workflow.d.ts +11 -0
- package/dist/cli/dispatch/workflow.js +67 -0
- package/dist/cli/help.d.ts +127 -0
- package/dist/cli/help.js +138 -0
- package/dist/cli/runtime.d.ts +16 -0
- package/dist/cli/runtime.js +38 -0
- package/dist/cli/validators.d.ts +17 -0
- package/dist/cli/validators.js +35 -0
- package/dist/commands/analyze.js +1 -3
- package/dist/commands/ci.d.ts +47 -0
- package/dist/commands/ci.js +380 -63
- package/dist/commands/doctor.js +2 -4
- package/dist/commands/init/report.d.ts +13 -0
- package/dist/commands/init/report.js +13 -0
- package/dist/commands/init/steps/agent-skills.d.ts +13 -0
- package/dist/commands/init/steps/agent-skills.js +45 -0
- package/dist/commands/init/steps/ai-sync.d.ts +15 -0
- package/dist/commands/init/steps/ai-sync.js +62 -0
- package/dist/commands/init/steps/ci.d.ts +24 -0
- package/dist/commands/init/steps/ci.js +74 -0
- package/dist/commands/init/steps/claude-md.d.ts +14 -0
- package/dist/commands/init/steps/claude-md.js +54 -0
- package/dist/commands/init/steps/code-graph.d.ts +16 -0
- package/dist/commands/init/steps/code-graph.js +68 -0
- package/dist/commands/init/steps/context-dir.d.ts +13 -0
- package/dist/commands/init/steps/context-dir.js +47 -0
- package/dist/commands/init/steps/docker-deploy.d.ts +16 -0
- package/dist/commands/init/steps/docker-deploy.js +60 -0
- package/dist/commands/init/steps/ghagga.d.ts +15 -0
- package/dist/commands/init/steps/ghagga.js +58 -0
- package/dist/commands/init/steps/git.d.ts +25 -0
- package/dist/commands/init/steps/git.js +81 -0
- package/dist/commands/init/steps/gitignore.d.ts +13 -0
- package/dist/commands/init/steps/gitignore.js +39 -0
- package/dist/commands/init/steps/local-ai.d.ts +14 -0
- package/dist/commands/init/steps/local-ai.js +53 -0
- package/dist/commands/init/steps/manifest.d.ts +13 -0
- package/dist/commands/init/steps/manifest.js +58 -0
- package/dist/commands/init/steps/memory.d.ts +15 -0
- package/dist/commands/init/steps/memory.js +60 -0
- package/dist/commands/init/steps/mock.d.ts +13 -0
- package/dist/commands/init/steps/mock.js +64 -0
- package/dist/commands/init/steps/sdd.d.ts +12 -0
- package/dist/commands/init/steps/sdd.js +40 -0
- package/dist/commands/init/steps/security.d.ts +28 -0
- package/dist/commands/init/steps/security.js +103 -0
- package/dist/commands/init/types.d.ts +30 -0
- package/dist/commands/init/types.js +2 -0
- package/dist/commands/init.d.ts +5 -4
- package/dist/commands/init.js +42 -630
- package/dist/commands/llmstxt.js +1 -1
- package/dist/commands/plugin.js +1 -1
- package/dist/commands/security.js +23 -4
- package/dist/commands/skills/analysis.d.ts +19 -0
- package/dist/commands/skills/analysis.js +211 -0
- package/dist/commands/skills/benchmark.d.ts +6 -0
- package/dist/commands/skills/benchmark.js +82 -0
- package/dist/commands/skills/constants.d.ts +10 -0
- package/dist/commands/skills/constants.js +27 -0
- package/dist/commands/skills/directives.d.ts +21 -0
- package/dist/commands/skills/directives.js +101 -0
- package/dist/commands/skills/parsing.d.ts +16 -0
- package/dist/commands/skills/parsing.js +108 -0
- package/dist/commands/skills/rules.d.ts +5 -0
- package/dist/commands/skills/rules.js +32 -0
- package/dist/commands/skills/scoring.d.ts +47 -0
- package/dist/commands/skills/scoring.js +306 -0
- package/dist/commands/skills.d.ts +7 -125
- package/dist/commands/skills.js +8 -874
- package/dist/commands/tdd-pipeline.js +1 -1
- package/dist/commands/tdd.js +1 -1
- package/dist/commands/workflow.js +2 -0
- package/dist/constants.js +2 -2
- package/dist/index.js +26 -473
- package/dist/lib/agent-skills.js +1 -1
- package/dist/{commands → lib}/atlassian-mcp.js +2 -2
- package/dist/lib/auto-skill-install.js +2 -3
- package/dist/lib/auto-wire.js +7 -7
- package/dist/lib/ci-config.d.ts +65 -0
- package/dist/lib/ci-config.js +297 -0
- package/dist/lib/claudemd.js +1 -1
- package/dist/lib/codex-export.js +4 -8
- package/dist/lib/common.js +2 -2
- package/dist/lib/context.js +2 -2
- package/dist/{commands → lib}/crash-recovery.js +4 -4
- package/dist/lib/docker.d.ts +22 -1
- package/dist/lib/docker.js +57 -49
- package/dist/lib/exec.d.ts +10 -0
- package/dist/lib/exec.js +11 -0
- package/dist/lib/frontmatter.js +2 -2
- package/dist/{commands → lib}/harness-audit.js +1 -1
- package/dist/{commands → lib}/parallel-batch.js +1 -3
- package/dist/lib/plugin.js +2 -4
- package/dist/{commands → lib}/secret-scanner.js +3 -1
- package/dist/{commands → lib}/security-analysis.js +4 -2
- package/dist/lib/skill-publish.js +23 -5
- package/dist/{commands → lib}/skill-scanner.d.ts +1 -1
- package/dist/{commands → lib}/skill-scanner.js +3 -3
- package/dist/lib/stack-detector.js +3 -3
- package/dist/{commands → lib}/supply-chain.js +1 -1
- package/dist/lib/template.js +2 -2
- package/dist/lib/workflow/discovery.d.ts +8 -0
- package/dist/lib/workflow/discovery.js +18 -1
- package/dist/lib/workflow/parser.js +15 -15
- package/dist/lib/workflow/validator.js +4 -4
- package/dist/tasks/index.d.ts +3 -3
- package/dist/tasks/index.js +3 -3
- package/dist/tasks/scaffold-tasks.d.ts +1 -1
- package/dist/tasks/scaffold-tasks.js +10 -10
- package/dist/tasks/task-id.d.ts +1 -1
- package/dist/tasks/task-id.js +9 -5
- package/dist/tasks/task-tracker.d.ts +1 -1
- package/dist/tasks/task-tracker.js +7 -7
- package/dist/ui/App.js +1 -1
- package/dist/ui/CIContext.js +1 -2
- package/dist/ui/Header.js +4 -4
- package/dist/ui/NameInput.js +1 -1
- package/dist/ui/Progress.d.ts +1 -1
- package/dist/ui/Progress.js +1 -1
- package/dist/ui/StackSelector.js +1 -1
- package/lib/common.psm1 +167 -0
- package/lib/common.sh +30 -0
- package/package.json +38 -19
- /package/dist/{commands → lib}/atlassian-mcp.d.ts +0 -0
- /package/dist/{commands → lib}/crash-recovery.d.ts +0 -0
- /package/dist/{commands → lib}/harness-audit.d.ts +0 -0
- /package/dist/{commands → lib}/parallel-batch.d.ts +0 -0
- /package/dist/{commands → lib}/secret-scanner.d.ts +0 -0
- /package/dist/{commands → lib}/security-analysis.d.ts +0 -0
- /package/dist/{commands → lib}/supply-chain.d.ts +0 -0
- /package/dist/{commands → lib}/team-presets.d.ts +0 -0
- /package/dist/{commands → lib}/team-presets.js +0 -0
package/dist/commands/tdd.js
CHANGED
|
@@ -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 {
|
|
3
|
-
import { render } from "ink";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
4
3
|
import meow from "meow";
|
|
5
|
-
import {
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
import
|
|
10
|
-
import
|
|
11
|
-
import
|
|
12
|
-
import
|
|
13
|
-
import {
|
|
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
|
-
|
|
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
|
|
145
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
230
|
-
React.createElement(Doctor, null)), { stdin: inkStdin });
|
|
34
|
+
handleDoctor(cli, { inkStdin, isCI });
|
|
231
35
|
break;
|
|
232
36
|
}
|
|
233
37
|
case "analyze": {
|
|
234
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|
package/dist/lib/agent-skills.js
CHANGED
|
@@ -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
|
|
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.
|
package/dist/lib/auto-wire.js
CHANGED
|
@@ -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()
|
|
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
|
|
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
|
|
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
|
|
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
|
|
236
|
+
return `${before}\n\n${after}`;
|
|
237
237
|
}
|
|
238
|
-
return before
|
|
238
|
+
return `${before}\n`;
|
|
239
239
|
}
|
|
240
240
|
//# sourceMappingURL=auto-wire.js.map
|