javi-forge 1.6.0 → 1.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/analyze.d.ts +1 -1
- package/dist/commands/analyze.js +15 -15
- package/dist/commands/atlassian-mcp.d.ts +42 -0
- package/dist/commands/atlassian-mcp.js +98 -0
- package/dist/commands/ci.d.ts +3 -3
- package/dist/commands/ci.js +185 -147
- package/dist/commands/crash-recovery.d.ts +34 -0
- package/dist/commands/crash-recovery.js +123 -0
- package/dist/commands/doctor.d.ts +2 -2
- package/dist/commands/doctor.js +113 -61
- package/dist/commands/harness-audit.d.ts +35 -0
- package/dist/commands/harness-audit.js +277 -0
- package/dist/commands/init.d.ts +1 -1
- package/dist/commands/init.js +384 -141
- package/dist/commands/llmstxt.d.ts +1 -1
- package/dist/commands/llmstxt.js +36 -34
- package/dist/commands/parallel-batch.d.ts +42 -0
- package/dist/commands/parallel-batch.js +90 -0
- package/dist/commands/plugin.d.ts +10 -1
- package/dist/commands/plugin.js +92 -47
- package/dist/commands/secret-scanner.d.ts +30 -0
- package/dist/commands/secret-scanner.js +272 -0
- package/dist/commands/security-analysis.d.ts +74 -0
- package/dist/commands/security-analysis.js +487 -0
- package/dist/commands/security.d.ts +11 -5
- package/dist/commands/security.js +216 -76
- package/dist/commands/skill-scanner.d.ts +63 -0
- package/dist/commands/skill-scanner.js +383 -0
- package/dist/commands/skills.d.ts +62 -5
- package/dist/commands/skills.js +439 -54
- package/dist/commands/supply-chain.d.ts +23 -0
- package/dist/commands/supply-chain.js +126 -0
- package/dist/commands/tdd-pipeline.d.ts +17 -0
- package/dist/commands/tdd-pipeline.js +144 -0
- package/dist/commands/tdd.d.ts +1 -1
- package/dist/commands/tdd.js +21 -18
- package/dist/commands/team-presets.d.ts +53 -0
- package/dist/commands/team-presets.js +201 -0
- package/dist/commands/workflow.d.ts +23 -0
- package/dist/commands/workflow.js +114 -0
- package/dist/constants.d.ts +15 -1
- package/dist/constants.js +161 -122
- package/dist/index.js +308 -98
- package/dist/lib/agent-skills.d.ts +36 -1
- package/dist/lib/agent-skills.js +168 -19
- package/dist/lib/auto-skill-install.d.ts +37 -0
- package/dist/lib/auto-skill-install.js +92 -0
- package/dist/lib/auto-wire.d.ts +20 -0
- package/dist/lib/auto-wire.js +240 -0
- package/dist/lib/claudemd.d.ts +13 -1
- package/dist/lib/claudemd.js +174 -24
- package/dist/lib/codex-export.d.ts +1 -1
- package/dist/lib/codex-export.js +29 -31
- package/dist/lib/common.d.ts +1 -1
- package/dist/lib/common.js +52 -44
- package/dist/lib/context.d.ts +17 -2
- package/dist/lib/context.js +142 -13
- package/dist/lib/docker.d.ts +1 -1
- package/dist/lib/docker.js +141 -112
- package/dist/lib/frontmatter.d.ts +1 -1
- package/dist/lib/frontmatter.js +29 -15
- package/dist/lib/plugin.d.ts +9 -3
- package/dist/lib/plugin.js +128 -69
- package/dist/lib/skill-publish.d.ts +40 -0
- package/dist/lib/skill-publish.js +146 -0
- package/dist/lib/stack-detector.d.ts +38 -0
- package/dist/lib/stack-detector.js +207 -0
- package/dist/lib/template.d.ts +16 -1
- package/dist/lib/template.js +46 -17
- package/dist/lib/workflow/discovery.d.ts +19 -0
- package/dist/lib/workflow/discovery.js +68 -0
- package/dist/lib/workflow/index.d.ts +5 -0
- package/dist/lib/workflow/index.js +5 -0
- package/dist/lib/workflow/parser.d.ts +16 -0
- package/dist/lib/workflow/parser.js +198 -0
- package/dist/lib/workflow/renderer.d.ts +9 -0
- package/dist/lib/workflow/renderer.js +152 -0
- package/dist/lib/workflow/validator.d.ts +10 -0
- package/dist/lib/workflow/validator.js +189 -0
- package/dist/tasks/index.d.ts +4 -0
- package/dist/tasks/index.js +4 -0
- package/dist/tasks/scaffold-tasks.d.ts +3 -0
- package/dist/tasks/scaffold-tasks.js +14 -0
- package/dist/tasks/task-id.d.ts +30 -0
- package/dist/tasks/task-id.js +55 -0
- package/dist/tasks/task-tracker.d.ts +15 -0
- package/dist/tasks/task-tracker.js +81 -0
- package/dist/types/index.d.ts +134 -6
- package/dist/types/index.js +11 -1
- package/dist/ui/AnalyzeUI.d.ts +1 -1
- package/dist/ui/AnalyzeUI.js +38 -39
- package/dist/ui/App.d.ts +5 -3
- package/dist/ui/App.js +86 -46
- package/dist/ui/AutoSkills.d.ts +9 -0
- package/dist/ui/AutoSkills.js +124 -0
- package/dist/ui/CI.d.ts +2 -2
- package/dist/ui/CI.js +24 -26
- package/dist/ui/CIContext.d.ts +1 -1
- package/dist/ui/CIContext.js +3 -2
- package/dist/ui/CISelector.d.ts +2 -2
- package/dist/ui/CISelector.js +23 -15
- package/dist/ui/Doctor.d.ts +1 -1
- package/dist/ui/Doctor.js +35 -29
- package/dist/ui/Header.d.ts +1 -1
- package/dist/ui/Header.js +14 -14
- package/dist/ui/HookProfileSelector.d.ts +9 -0
- package/dist/ui/HookProfileSelector.js +54 -0
- package/dist/ui/LlmsTxt.d.ts +1 -1
- package/dist/ui/LlmsTxt.js +31 -22
- package/dist/ui/MemorySelector.d.ts +2 -2
- package/dist/ui/MemorySelector.js +28 -16
- package/dist/ui/NameInput.d.ts +1 -1
- package/dist/ui/NameInput.js +21 -21
- package/dist/ui/OptionSelector.d.ts +6 -2
- package/dist/ui/OptionSelector.js +83 -32
- package/dist/ui/Plugin.d.ts +4 -3
- package/dist/ui/Plugin.js +78 -35
- package/dist/ui/Progress.d.ts +3 -3
- package/dist/ui/Progress.js +23 -22
- package/dist/ui/Skills.d.ts +2 -2
- package/dist/ui/Skills.js +61 -32
- package/dist/ui/StackSelector.d.ts +2 -2
- package/dist/ui/StackSelector.js +26 -16
- package/dist/ui/Summary.d.ts +3 -3
- package/dist/ui/Summary.js +60 -50
- package/dist/ui/Welcome.d.ts +1 -1
- package/dist/ui/Welcome.js +15 -16
- package/dist/ui/theme.d.ts +1 -1
- package/dist/ui/theme.js +6 -6
- package/package.json +9 -6
- package/templates/common/atlassian/mcp-atlassian-snippet.json +16 -0
- package/templates/common/repoforge/mcp-repoforge-snippet.json +11 -0
- package/templates/common/repoforge/repoforge.yaml +34 -0
- package/templates/github/deploy-docker-zero-downtime.yml +140 -0
- package/templates/github/repoforge-graph.yml +45 -0
- package/templates/gitlab/deploy-docker-zero-downtime.yml +57 -0
- package/templates/local-ai/.env.example +17 -0
- package/templates/local-ai/docker-compose.yml +95 -0
- package/templates/security-hooks/claude-settings-security.json +30 -0
- package/templates/security-hooks/commit-msg-signing +29 -0
- package/templates/security-hooks/pre-commit-permissions +74 -0
- package/templates/security-hooks/pre-commit-secrets +74 -0
- package/templates/security-hooks/pre-push-branch-protection +62 -0
- package/templates/security-hooks/pre-push-deps +83 -0
- package/templates/security-hooks/pre-push-signing +67 -0
- package/templates/woodpecker/deploy-docker-zero-downtime.yml +50 -0
- package/templates/workflows/ci-pipeline.dot +15 -0
- package/templates/workflows/feature-flow.dot +21 -0
- package/templates/workflows/release.dot +16 -0
- package/dist/__integration__/helpers.d.ts +0 -20
- package/dist/__integration__/helpers.d.ts.map +0 -1
- package/dist/__integration__/helpers.js +0 -31
- package/dist/__integration__/helpers.js.map +0 -1
- package/dist/commands/analyze.d.ts.map +0 -1
- package/dist/commands/analyze.js.map +0 -1
- package/dist/commands/ci.d.ts.map +0 -1
- package/dist/commands/ci.js.map +0 -1
- package/dist/commands/doctor.d.ts.map +0 -1
- package/dist/commands/doctor.js.map +0 -1
- package/dist/commands/init.d.ts.map +0 -1
- package/dist/commands/init.js.map +0 -1
- package/dist/commands/llmstxt.d.ts.map +0 -1
- package/dist/commands/llmstxt.js.map +0 -1
- package/dist/commands/plugin.d.ts.map +0 -1
- package/dist/commands/plugin.js.map +0 -1
- package/dist/commands/security.d.ts.map +0 -1
- package/dist/commands/security.js.map +0 -1
- package/dist/commands/skills.d.ts.map +0 -1
- package/dist/commands/skills.js.map +0 -1
- package/dist/commands/tdd.d.ts.map +0 -1
- package/dist/commands/tdd.js.map +0 -1
- package/dist/constants.d.ts.map +0 -1
- package/dist/constants.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/lib/agent-skills.d.ts.map +0 -1
- package/dist/lib/agent-skills.js.map +0 -1
- package/dist/lib/claudemd.d.ts.map +0 -1
- package/dist/lib/claudemd.js.map +0 -1
- package/dist/lib/codex-export.d.ts.map +0 -1
- package/dist/lib/codex-export.js.map +0 -1
- package/dist/lib/common.d.ts.map +0 -1
- package/dist/lib/common.js.map +0 -1
- package/dist/lib/context.d.ts.map +0 -1
- package/dist/lib/context.js.map +0 -1
- package/dist/lib/docker.d.ts.map +0 -1
- package/dist/lib/docker.js.map +0 -1
- package/dist/lib/frontmatter.d.ts.map +0 -1
- package/dist/lib/frontmatter.js.map +0 -1
- package/dist/lib/plugin.d.ts.map +0 -1
- package/dist/lib/plugin.js.map +0 -1
- package/dist/lib/template.d.ts.map +0 -1
- package/dist/lib/template.js.map +0 -1
- package/dist/types/index.d.ts.map +0 -1
- package/dist/types/index.js.map +0 -1
- package/dist/ui/AnalyzeUI.d.ts.map +0 -1
- package/dist/ui/AnalyzeUI.js.map +0 -1
- package/dist/ui/App.d.ts.map +0 -1
- package/dist/ui/App.js.map +0 -1
- package/dist/ui/CI.d.ts.map +0 -1
- package/dist/ui/CI.js.map +0 -1
- package/dist/ui/CIContext.d.ts.map +0 -1
- package/dist/ui/CIContext.js.map +0 -1
- package/dist/ui/CISelector.d.ts.map +0 -1
- package/dist/ui/CISelector.js.map +0 -1
- package/dist/ui/Doctor.d.ts.map +0 -1
- package/dist/ui/Doctor.js.map +0 -1
- package/dist/ui/Header.d.ts.map +0 -1
- package/dist/ui/Header.js.map +0 -1
- package/dist/ui/LlmsTxt.d.ts.map +0 -1
- package/dist/ui/LlmsTxt.js.map +0 -1
- package/dist/ui/MemorySelector.d.ts.map +0 -1
- package/dist/ui/MemorySelector.js.map +0 -1
- package/dist/ui/NameInput.d.ts.map +0 -1
- package/dist/ui/NameInput.js.map +0 -1
- package/dist/ui/OptionSelector.d.ts.map +0 -1
- package/dist/ui/OptionSelector.js.map +0 -1
- package/dist/ui/Plugin.d.ts.map +0 -1
- package/dist/ui/Plugin.js.map +0 -1
- package/dist/ui/Progress.d.ts.map +0 -1
- package/dist/ui/Progress.js.map +0 -1
- package/dist/ui/Skills.d.ts.map +0 -1
- package/dist/ui/Skills.js.map +0 -1
- package/dist/ui/StackSelector.d.ts.map +0 -1
- package/dist/ui/StackSelector.js.map +0 -1
- package/dist/ui/Summary.d.ts.map +0 -1
- package/dist/ui/Summary.js.map +0 -1
- package/dist/ui/Welcome.d.ts.map +0 -1
- package/dist/ui/Welcome.js.map +0 -1
- package/dist/ui/theme.d.ts.map +0 -1
- package/dist/ui/theme.js.map +0 -1
package/dist/lib/plugin.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { InstalledPlugin, PluginRegistryEntry, PluginSyncResult, PluginValidationResult } from "../types/index.js";
|
|
2
2
|
/**
|
|
3
3
|
* Validate a plugin directory structure and manifest.
|
|
4
4
|
*/
|
|
@@ -37,8 +37,14 @@ export declare function searchRegistry(query?: string): Promise<PluginRegistryEn
|
|
|
37
37
|
*/
|
|
38
38
|
export declare function detectProjectPlugins(projectDir: string): Promise<string[]>;
|
|
39
39
|
/**
|
|
40
|
-
*
|
|
41
|
-
*
|
|
40
|
+
* Detect installed plugins with full metadata (including manifest).
|
|
41
|
+
* Used by auto-wiring to read plugin capabilities.
|
|
42
|
+
*/
|
|
43
|
+
export declare function detectProjectPluginsFull(projectDir: string): Promise<InstalledPlugin[]>;
|
|
44
|
+
/**
|
|
45
|
+
* Sync detected plugins into the project manifest and auto-wire
|
|
46
|
+
* their capabilities into CLAUDE.md and .claude/settings.json.
|
|
47
|
+
* Returns a report of added, removed, unchanged, wired, and unwired plugins.
|
|
42
48
|
*/
|
|
43
49
|
export declare function syncPlugins(projectDir: string, options?: {
|
|
44
50
|
dryRun?: boolean;
|
package/dist/lib/plugin.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import { promisify } from
|
|
5
|
-
import {
|
|
6
|
-
import { generateAgentSkillsManifest } from
|
|
1
|
+
import { execFile } from "child_process";
|
|
2
|
+
import fs from "fs-extra";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { promisify } from "util";
|
|
5
|
+
import { PLUGIN_ASSET_DIRS, PLUGIN_MANIFEST_FILE, PLUGIN_REGISTRY_URL, PLUGINS_DIR, } from "../constants.js";
|
|
6
|
+
import { generateAgentSkillsManifest } from "./agent-skills.js";
|
|
7
|
+
import { autoWirePlugins } from "./auto-wire.js";
|
|
7
8
|
const execFileAsync = promisify(execFile);
|
|
8
9
|
const KEBAB_RE = /^[a-z0-9]+(-[a-z0-9]+)*$/;
|
|
9
10
|
const SEMVER_RE = /^\d+\.\d+\.\d+$/;
|
|
@@ -15,49 +16,60 @@ export async function validatePlugin(pluginDir) {
|
|
|
15
16
|
const errors = [];
|
|
16
17
|
// Check plugin.json exists
|
|
17
18
|
const manifestPath = path.join(pluginDir, PLUGIN_MANIFEST_FILE);
|
|
18
|
-
if (!await fs.pathExists(manifestPath)) {
|
|
19
|
+
if (!(await fs.pathExists(manifestPath))) {
|
|
19
20
|
return {
|
|
20
21
|
valid: false,
|
|
21
|
-
errors: [
|
|
22
|
+
errors: [
|
|
23
|
+
{ path: PLUGIN_MANIFEST_FILE, message: "plugin.json not found" },
|
|
24
|
+
],
|
|
22
25
|
manifest: null,
|
|
23
26
|
};
|
|
24
27
|
}
|
|
25
28
|
// Parse manifest
|
|
26
29
|
let manifest;
|
|
27
30
|
try {
|
|
28
|
-
manifest = await fs.readJson(manifestPath);
|
|
31
|
+
manifest = (await fs.readJson(manifestPath));
|
|
29
32
|
}
|
|
30
33
|
catch {
|
|
31
34
|
return {
|
|
32
35
|
valid: false,
|
|
33
|
-
errors: [{ path: PLUGIN_MANIFEST_FILE, message:
|
|
36
|
+
errors: [{ path: PLUGIN_MANIFEST_FILE, message: "invalid JSON" }],
|
|
34
37
|
manifest: null,
|
|
35
38
|
};
|
|
36
39
|
}
|
|
37
40
|
// Validate required fields
|
|
38
|
-
if (typeof manifest.name !==
|
|
39
|
-
errors.push({ path:
|
|
41
|
+
if (typeof manifest.name !== "string" || !manifest.name) {
|
|
42
|
+
errors.push({ path: "name", message: "name is required" });
|
|
40
43
|
}
|
|
41
44
|
else if (!KEBAB_RE.test(manifest.name)) {
|
|
42
|
-
errors.push({ path:
|
|
45
|
+
errors.push({ path: "name", message: "name must be kebab-case" });
|
|
43
46
|
}
|
|
44
47
|
else if (manifest.name.length < 2 || manifest.name.length > 60) {
|
|
45
|
-
errors.push({ path:
|
|
48
|
+
errors.push({ path: "name", message: "name must be 2-60 characters" });
|
|
46
49
|
}
|
|
47
|
-
if (typeof manifest.version !==
|
|
48
|
-
errors.push({ path:
|
|
50
|
+
if (typeof manifest.version !== "string" || !manifest.version) {
|
|
51
|
+
errors.push({ path: "version", message: "version is required" });
|
|
49
52
|
}
|
|
50
53
|
else if (!SEMVER_RE.test(manifest.version)) {
|
|
51
|
-
errors.push({
|
|
54
|
+
errors.push({
|
|
55
|
+
path: "version",
|
|
56
|
+
message: "version must be semver (e.g. 1.0.0)",
|
|
57
|
+
});
|
|
52
58
|
}
|
|
53
|
-
if (typeof manifest.description !==
|
|
54
|
-
errors.push({ path:
|
|
59
|
+
if (typeof manifest.description !== "string" || !manifest.description) {
|
|
60
|
+
errors.push({ path: "description", message: "description is required" });
|
|
55
61
|
}
|
|
56
62
|
else if (manifest.description.length < 10) {
|
|
57
|
-
errors.push({
|
|
63
|
+
errors.push({
|
|
64
|
+
path: "description",
|
|
65
|
+
message: "description must be at least 10 characters",
|
|
66
|
+
});
|
|
58
67
|
}
|
|
59
68
|
else if (manifest.description.length > 200) {
|
|
60
|
-
errors.push({
|
|
69
|
+
errors.push({
|
|
70
|
+
path: "description",
|
|
71
|
+
message: "description must be at most 200 characters",
|
|
72
|
+
});
|
|
61
73
|
}
|
|
62
74
|
// Validate asset directories actually exist when declared
|
|
63
75
|
for (const assetType of PLUGIN_ASSET_DIRS) {
|
|
@@ -65,26 +77,36 @@ export async function validatePlugin(pluginDir) {
|
|
|
65
77
|
if (!Array.isArray(declared) || declared.length === 0)
|
|
66
78
|
continue;
|
|
67
79
|
const assetDir = path.join(pluginDir, assetType);
|
|
68
|
-
if (!await fs.pathExists(assetDir)) {
|
|
69
|
-
errors.push({
|
|
80
|
+
if (!(await fs.pathExists(assetDir))) {
|
|
81
|
+
errors.push({
|
|
82
|
+
path: assetType,
|
|
83
|
+
message: `declared ${assetType}/ directory not found`,
|
|
84
|
+
});
|
|
70
85
|
continue;
|
|
71
86
|
}
|
|
72
87
|
// Check each declared asset exists
|
|
73
88
|
for (const entry of declared) {
|
|
74
89
|
const entryPath = path.join(assetDir, entry);
|
|
75
|
-
if (!await fs.pathExists(entryPath)) {
|
|
76
|
-
errors.push({
|
|
90
|
+
if (!(await fs.pathExists(entryPath))) {
|
|
91
|
+
errors.push({
|
|
92
|
+
path: `${assetType}/${entry}`,
|
|
93
|
+
message: `declared entry not found`,
|
|
94
|
+
});
|
|
77
95
|
}
|
|
78
96
|
}
|
|
79
97
|
}
|
|
80
98
|
// Validate tags
|
|
81
99
|
if (manifest.tags && !Array.isArray(manifest.tags)) {
|
|
82
|
-
errors.push({ path:
|
|
100
|
+
errors.push({ path: "tags", message: "tags must be an array" });
|
|
83
101
|
}
|
|
84
102
|
else if (manifest.tags && manifest.tags.length > 10) {
|
|
85
|
-
errors.push({ path:
|
|
103
|
+
errors.push({ path: "tags", message: "max 10 tags allowed" });
|
|
86
104
|
}
|
|
87
|
-
return {
|
|
105
|
+
return {
|
|
106
|
+
valid: errors.length === 0,
|
|
107
|
+
errors,
|
|
108
|
+
manifest: errors.length === 0 ? manifest : manifest,
|
|
109
|
+
};
|
|
88
110
|
}
|
|
89
111
|
// ── Installation ────────────────────────────────────────────────────────────
|
|
90
112
|
/**
|
|
@@ -96,23 +118,36 @@ export async function installPlugin(source, options = {}) {
|
|
|
96
118
|
// Normalize source to a git URL
|
|
97
119
|
const gitUrl = normalizeGitUrl(source);
|
|
98
120
|
if (!gitUrl) {
|
|
99
|
-
return {
|
|
121
|
+
return {
|
|
122
|
+
success: false,
|
|
123
|
+
error: `invalid source: ${source}. Use org/repo or a GitHub URL`,
|
|
124
|
+
};
|
|
100
125
|
}
|
|
101
126
|
// Clone to temp
|
|
102
|
-
const tmpDir = path.join(PLUGINS_DIR,
|
|
127
|
+
const tmpDir = path.join(PLUGINS_DIR, ".tmp", `install-${Date.now()}`);
|
|
103
128
|
try {
|
|
104
129
|
if (!dryRun) {
|
|
105
130
|
await fs.ensureDir(tmpDir);
|
|
106
|
-
await execFileAsync(
|
|
131
|
+
await execFileAsync("git", ["clone", "--depth", "1", gitUrl, tmpDir], {
|
|
107
132
|
timeout: 60_000,
|
|
108
133
|
});
|
|
109
134
|
}
|
|
110
135
|
// Validate
|
|
111
136
|
const validation = dryRun
|
|
112
|
-
? {
|
|
137
|
+
? {
|
|
138
|
+
valid: true,
|
|
139
|
+
errors: [],
|
|
140
|
+
manifest: {
|
|
141
|
+
name: source.split("/").pop() ?? "unknown",
|
|
142
|
+
version: "0.0.0",
|
|
143
|
+
description: "dry-run placeholder",
|
|
144
|
+
},
|
|
145
|
+
}
|
|
113
146
|
: await validatePlugin(tmpDir);
|
|
114
147
|
if (!validation.valid || !validation.manifest) {
|
|
115
|
-
const msgs = validation.errors
|
|
148
|
+
const msgs = validation.errors
|
|
149
|
+
.map((e) => ` ${e.path}: ${e.message}`)
|
|
150
|
+
.join("\n");
|
|
116
151
|
return { success: false, error: `validation failed:\n${msgs}` };
|
|
117
152
|
}
|
|
118
153
|
const pluginName = validation.manifest.name;
|
|
@@ -131,7 +166,7 @@ export async function installPlugin(source, options = {}) {
|
|
|
131
166
|
source,
|
|
132
167
|
manifest: validation.manifest,
|
|
133
168
|
};
|
|
134
|
-
await fs.writeJson(path.join(destDir,
|
|
169
|
+
await fs.writeJson(path.join(destDir, ".installed.json"), installedPlugin, { spaces: 2 });
|
|
135
170
|
// Generate Agent Skills spec manifest for cross-agent compatibility
|
|
136
171
|
await generateAgentSkillsManifest(destDir, source).catch(() => { });
|
|
137
172
|
}
|
|
@@ -143,7 +178,7 @@ export async function installPlugin(source, options = {}) {
|
|
|
143
178
|
}
|
|
144
179
|
finally {
|
|
145
180
|
// Clean up tmp if it still exists (error path)
|
|
146
|
-
if (!dryRun && await fs.pathExists(tmpDir)) {
|
|
181
|
+
if (!dryRun && (await fs.pathExists(tmpDir))) {
|
|
147
182
|
await fs.remove(tmpDir).catch(() => { });
|
|
148
183
|
}
|
|
149
184
|
}
|
|
@@ -153,7 +188,7 @@ export async function installPlugin(source, options = {}) {
|
|
|
153
188
|
*/
|
|
154
189
|
export async function removePlugin(name, options = {}) {
|
|
155
190
|
const pluginDir = path.join(PLUGINS_DIR, name);
|
|
156
|
-
if (!await fs.pathExists(pluginDir)) {
|
|
191
|
+
if (!(await fs.pathExists(pluginDir))) {
|
|
157
192
|
return { success: false, error: `plugin "${name}" is not installed` };
|
|
158
193
|
}
|
|
159
194
|
if (!options.dryRun) {
|
|
@@ -166,20 +201,22 @@ export async function removePlugin(name, options = {}) {
|
|
|
166
201
|
* List all installed plugins.
|
|
167
202
|
*/
|
|
168
203
|
export async function listInstalledPlugins() {
|
|
169
|
-
if (!await fs.pathExists(PLUGINS_DIR))
|
|
204
|
+
if (!(await fs.pathExists(PLUGINS_DIR)))
|
|
170
205
|
return [];
|
|
171
206
|
const entries = await fs.readdir(PLUGINS_DIR);
|
|
172
207
|
const plugins = [];
|
|
173
208
|
for (const entry of entries) {
|
|
174
|
-
if (entry.startsWith(
|
|
209
|
+
if (entry.startsWith("."))
|
|
175
210
|
continue;
|
|
176
|
-
const metaPath = path.join(PLUGINS_DIR, entry,
|
|
211
|
+
const metaPath = path.join(PLUGINS_DIR, entry, ".installed.json");
|
|
177
212
|
if (await fs.pathExists(metaPath)) {
|
|
178
213
|
try {
|
|
179
|
-
const meta = await fs.readJson(metaPath);
|
|
214
|
+
const meta = (await fs.readJson(metaPath));
|
|
180
215
|
plugins.push(meta);
|
|
181
216
|
}
|
|
182
|
-
catch {
|
|
217
|
+
catch {
|
|
218
|
+
/* skip corrupt entries */
|
|
219
|
+
}
|
|
183
220
|
}
|
|
184
221
|
}
|
|
185
222
|
return plugins;
|
|
@@ -193,13 +230,13 @@ export async function searchRegistry(query) {
|
|
|
193
230
|
if (!response.ok) {
|
|
194
231
|
return [];
|
|
195
232
|
}
|
|
196
|
-
const registry = await response.json();
|
|
233
|
+
const registry = (await response.json());
|
|
197
234
|
let plugins = registry.plugins ?? [];
|
|
198
235
|
if (query) {
|
|
199
236
|
const q = query.toLowerCase();
|
|
200
|
-
plugins = plugins.filter(p => p.id.toLowerCase().includes(q) ||
|
|
237
|
+
plugins = plugins.filter((p) => p.id.toLowerCase().includes(q) ||
|
|
201
238
|
p.description.toLowerCase().includes(q) ||
|
|
202
|
-
p.tags.some(t => t.toLowerCase().includes(q)));
|
|
239
|
+
p.tags.some((t) => t.toLowerCase().includes(q)));
|
|
203
240
|
}
|
|
204
241
|
return plugins;
|
|
205
242
|
}
|
|
@@ -213,47 +250,59 @@ export async function searchRegistry(query) {
|
|
|
213
250
|
* Returns an array of plugin names (sorted alphabetically).
|
|
214
251
|
*/
|
|
215
252
|
export async function detectProjectPlugins(projectDir) {
|
|
216
|
-
const
|
|
217
|
-
|
|
253
|
+
const full = await detectProjectPluginsFull(projectDir);
|
|
254
|
+
return full.map((p) => p.name).sort();
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Detect installed plugins with full metadata (including manifest).
|
|
258
|
+
* Used by auto-wiring to read plugin capabilities.
|
|
259
|
+
*/
|
|
260
|
+
export async function detectProjectPluginsFull(projectDir) {
|
|
261
|
+
const pluginsDir = path.join(projectDir, ".javi-forge", "plugins");
|
|
262
|
+
if (!(await fs.pathExists(pluginsDir)))
|
|
218
263
|
return [];
|
|
219
264
|
const entries = await fs.readdir(pluginsDir);
|
|
220
265
|
const plugins = [];
|
|
221
266
|
for (const entry of entries) {
|
|
222
|
-
if (entry.startsWith(
|
|
267
|
+
if (entry.startsWith("."))
|
|
223
268
|
continue;
|
|
224
|
-
const metaPath = path.join(pluginsDir, entry,
|
|
269
|
+
const metaPath = path.join(pluginsDir, entry, ".installed.json");
|
|
225
270
|
if (await fs.pathExists(metaPath)) {
|
|
226
271
|
try {
|
|
227
|
-
const meta = await fs.readJson(metaPath);
|
|
272
|
+
const meta = (await fs.readJson(metaPath));
|
|
228
273
|
if (meta.name) {
|
|
229
|
-
plugins.push(meta
|
|
274
|
+
plugins.push(meta);
|
|
230
275
|
}
|
|
231
276
|
}
|
|
232
|
-
catch {
|
|
277
|
+
catch {
|
|
278
|
+
/* skip corrupt entries */
|
|
279
|
+
}
|
|
233
280
|
}
|
|
234
281
|
}
|
|
235
|
-
return plugins.sort();
|
|
282
|
+
return plugins.sort((a, b) => a.name.localeCompare(b.name));
|
|
236
283
|
}
|
|
237
284
|
/**
|
|
238
|
-
* Sync detected plugins into the project manifest
|
|
239
|
-
*
|
|
285
|
+
* Sync detected plugins into the project manifest and auto-wire
|
|
286
|
+
* their capabilities into CLAUDE.md and .claude/settings.json.
|
|
287
|
+
* Returns a report of added, removed, unchanged, wired, and unwired plugins.
|
|
240
288
|
*/
|
|
241
289
|
export async function syncPlugins(projectDir, options = {}) {
|
|
242
290
|
const { dryRun = false } = options;
|
|
243
|
-
const
|
|
244
|
-
const
|
|
291
|
+
const detectedFull = await detectProjectPluginsFull(projectDir);
|
|
292
|
+
const detected = detectedFull.map((p) => p.name).sort();
|
|
293
|
+
const manifestPath = path.join(projectDir, ".javi-forge", "manifest.json");
|
|
245
294
|
let manifest;
|
|
246
295
|
if (await fs.pathExists(manifestPath)) {
|
|
247
|
-
manifest = await fs.readJson(manifestPath);
|
|
296
|
+
manifest = (await fs.readJson(manifestPath));
|
|
248
297
|
}
|
|
249
298
|
else {
|
|
250
299
|
// No manifest yet — treat current plugins as empty
|
|
251
300
|
manifest = {
|
|
252
|
-
version:
|
|
301
|
+
version: "0.1.0",
|
|
253
302
|
projectName: path.basename(projectDir),
|
|
254
|
-
stack:
|
|
255
|
-
ciProvider:
|
|
256
|
-
memory:
|
|
303
|
+
stack: "node",
|
|
304
|
+
ciProvider: "github",
|
|
305
|
+
memory: "none",
|
|
257
306
|
createdAt: new Date().toISOString(),
|
|
258
307
|
updatedAt: new Date().toISOString(),
|
|
259
308
|
modules: [],
|
|
@@ -261,16 +310,26 @@ export async function syncPlugins(projectDir, options = {}) {
|
|
|
261
310
|
}
|
|
262
311
|
const previous = new Set(manifest.plugins ?? []);
|
|
263
312
|
const current = new Set(detected);
|
|
264
|
-
const added = detected.filter(p => !previous.has(p));
|
|
265
|
-
const removed = [...previous].filter(p => !current.has(p));
|
|
266
|
-
const unchanged = detected.filter(p => previous.has(p));
|
|
313
|
+
const added = detected.filter((p) => !previous.has(p));
|
|
314
|
+
const removed = [...previous].filter((p) => !current.has(p));
|
|
315
|
+
const unchanged = detected.filter((p) => previous.has(p));
|
|
267
316
|
if (!dryRun && (added.length > 0 || removed.length > 0)) {
|
|
268
317
|
manifest.plugins = detected;
|
|
269
318
|
manifest.updatedAt = new Date().toISOString();
|
|
270
319
|
await fs.ensureDir(path.dirname(manifestPath));
|
|
271
320
|
await fs.writeJson(manifestPath, manifest, { spaces: 2 });
|
|
272
321
|
}
|
|
273
|
-
|
|
322
|
+
// ── Auto-wire plugin capabilities ──────────────────────────────────
|
|
323
|
+
const wireResult = await autoWirePlugins(projectDir, detectedFull, {
|
|
324
|
+
dryRun,
|
|
325
|
+
});
|
|
326
|
+
return {
|
|
327
|
+
added,
|
|
328
|
+
removed,
|
|
329
|
+
unchanged,
|
|
330
|
+
wired: wireResult.wired,
|
|
331
|
+
unwired: wireResult.unwired,
|
|
332
|
+
};
|
|
274
333
|
}
|
|
275
334
|
// ── Helpers ─────────────────────────────────────────────────────────────────
|
|
276
335
|
/**
|
|
@@ -279,15 +338,15 @@ export async function syncPlugins(projectDir, options = {}) {
|
|
|
279
338
|
*/
|
|
280
339
|
export function normalizeGitUrl(source) {
|
|
281
340
|
// Already a full URL
|
|
282
|
-
if (source.startsWith(
|
|
283
|
-
return source.endsWith(
|
|
341
|
+
if (source.startsWith("https://github.com/")) {
|
|
342
|
+
return source.endsWith(".git") ? source : `${source}.git`;
|
|
284
343
|
}
|
|
285
344
|
// github.com/org/repo
|
|
286
|
-
if (source.startsWith(
|
|
345
|
+
if (source.startsWith("github.com/")) {
|
|
287
346
|
return `https://${source}.git`;
|
|
288
347
|
}
|
|
289
348
|
// org/repo shorthand
|
|
290
|
-
const parts = source.split(
|
|
349
|
+
const parts = source.split("/");
|
|
291
350
|
if (parts.length === 2 && parts[0] && parts[1]) {
|
|
292
351
|
return `https://github.com/${parts[0]}/${parts[1]}.git`;
|
|
293
352
|
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { PluginManifest } from "../types/index.js";
|
|
2
|
+
export interface SkillPublishResult {
|
|
3
|
+
success: boolean;
|
|
4
|
+
/** Path to the generated plugin.json */
|
|
5
|
+
pluginJsonPath?: string;
|
|
6
|
+
/** The generated manifest */
|
|
7
|
+
manifest?: PluginManifest;
|
|
8
|
+
error?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface SkillPublishOptions {
|
|
11
|
+
/** Path to the skill directory (contains SKILL.md) */
|
|
12
|
+
skillDir: string;
|
|
13
|
+
/** Author name (optional, falls back to frontmatter or git user) */
|
|
14
|
+
author?: string;
|
|
15
|
+
/** Repository URL (optional) */
|
|
16
|
+
repository?: string;
|
|
17
|
+
/** Tags for marketplace discovery */
|
|
18
|
+
tags?: string[];
|
|
19
|
+
/** If true, skip writing files */
|
|
20
|
+
dryRun?: boolean;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Package a skill directory for marketplace distribution.
|
|
24
|
+
*
|
|
25
|
+
* Reads the SKILL.md, extracts metadata from frontmatter, and generates
|
|
26
|
+
* a plugin.json compatible with both `javi-forge plugin install` and
|
|
27
|
+
* `claude plugin install` (Anthropic's plugin format).
|
|
28
|
+
*
|
|
29
|
+
* Expected input structure:
|
|
30
|
+
* skill-name/
|
|
31
|
+
* SKILL.md
|
|
32
|
+
* (optional other files)
|
|
33
|
+
*
|
|
34
|
+
* Output:
|
|
35
|
+
* skill-name/
|
|
36
|
+
* SKILL.md
|
|
37
|
+
* plugin.json ← generated
|
|
38
|
+
*/
|
|
39
|
+
export declare function publishSkill(options: SkillPublishOptions): Promise<SkillPublishResult>;
|
|
40
|
+
//# sourceMappingURL=skill-publish.d.ts.map
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import fs from "fs-extra";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { PLUGIN_MANIFEST_FILE } from "../constants.js";
|
|
4
|
+
import { parseFrontmatter } from "./frontmatter.js";
|
|
5
|
+
// ── Core ───────────────────────────────────────────────────────────────────
|
|
6
|
+
/**
|
|
7
|
+
* Package a skill directory for marketplace distribution.
|
|
8
|
+
*
|
|
9
|
+
* Reads the SKILL.md, extracts metadata from frontmatter, and generates
|
|
10
|
+
* a plugin.json compatible with both `javi-forge plugin install` and
|
|
11
|
+
* `claude plugin install` (Anthropic's plugin format).
|
|
12
|
+
*
|
|
13
|
+
* Expected input structure:
|
|
14
|
+
* skill-name/
|
|
15
|
+
* SKILL.md
|
|
16
|
+
* (optional other files)
|
|
17
|
+
*
|
|
18
|
+
* Output:
|
|
19
|
+
* skill-name/
|
|
20
|
+
* SKILL.md
|
|
21
|
+
* plugin.json ← generated
|
|
22
|
+
*/
|
|
23
|
+
export async function publishSkill(options) {
|
|
24
|
+
const { skillDir, author, repository, tags = [], dryRun = false } = options;
|
|
25
|
+
// Validate skill directory
|
|
26
|
+
const skillMdPath = path.join(skillDir, "SKILL.md");
|
|
27
|
+
if (!(await fs.pathExists(skillMdPath))) {
|
|
28
|
+
return { success: false, error: `SKILL.md not found in ${skillDir}` };
|
|
29
|
+
}
|
|
30
|
+
// Read and parse SKILL.md
|
|
31
|
+
let raw;
|
|
32
|
+
try {
|
|
33
|
+
raw = await fs.readFile(skillMdPath, "utf-8");
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return { success: false, error: "Failed to read SKILL.md" };
|
|
37
|
+
}
|
|
38
|
+
const parsed = parseFrontmatter(raw);
|
|
39
|
+
const frontmatter = parsed?.data ?? {};
|
|
40
|
+
// Extract metadata
|
|
41
|
+
const name = frontmatter["name"] ?? path.basename(skillDir);
|
|
42
|
+
const version = frontmatter["version"] ?? "1.0.0";
|
|
43
|
+
const description = frontmatter["description"] ?? `${name} AI skill`;
|
|
44
|
+
// Validate name format (kebab-case)
|
|
45
|
+
if (!/^[a-z0-9]+(-[a-z0-9]+)*$/.test(name)) {
|
|
46
|
+
return {
|
|
47
|
+
success: false,
|
|
48
|
+
error: `Skill name "${name}" must be kebab-case (e.g., "react-19", "tailwind-4")`,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
// Validate description length
|
|
52
|
+
if (description.length < 10) {
|
|
53
|
+
return {
|
|
54
|
+
success: false,
|
|
55
|
+
error: "Description must be at least 10 characters",
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
// Build plugin.json manifest
|
|
59
|
+
const manifest = {
|
|
60
|
+
name,
|
|
61
|
+
version,
|
|
62
|
+
description: description.length > 200
|
|
63
|
+
? description.slice(0, 197) + "..."
|
|
64
|
+
: description,
|
|
65
|
+
skills: [name],
|
|
66
|
+
tags: tags.length > 0 ? tags : extractTagsFromDescription(description),
|
|
67
|
+
...(author ? { author } : {}),
|
|
68
|
+
...(repository ? { repository } : {}),
|
|
69
|
+
};
|
|
70
|
+
if (!dryRun) {
|
|
71
|
+
const pluginJsonPath = path.join(skillDir, PLUGIN_MANIFEST_FILE);
|
|
72
|
+
await fs.writeJson(pluginJsonPath, manifest, { spaces: 2 });
|
|
73
|
+
// Also create a skills/ subdirectory structure for plugin compatibility
|
|
74
|
+
const skillsSubdir = path.join(skillDir, "skills", name);
|
|
75
|
+
if (!(await fs.pathExists(skillsSubdir))) {
|
|
76
|
+
await fs.ensureDir(skillsSubdir);
|
|
77
|
+
// Symlink or copy SKILL.md into skills/name/
|
|
78
|
+
const targetSkillMd = path.join(skillsSubdir, "SKILL.md");
|
|
79
|
+
if (!(await fs.pathExists(targetSkillMd))) {
|
|
80
|
+
await fs.copy(skillMdPath, targetSkillMd);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return { success: true, pluginJsonPath, manifest };
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
success: true,
|
|
87
|
+
pluginJsonPath: path.join(skillDir, PLUGIN_MANIFEST_FILE),
|
|
88
|
+
manifest,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
// ── Helpers ────────────────────────────────────────────────────────────────
|
|
92
|
+
/**
|
|
93
|
+
* Extract reasonable tags from a skill description.
|
|
94
|
+
* Looks for common tech keywords.
|
|
95
|
+
*/
|
|
96
|
+
function extractTagsFromDescription(description) {
|
|
97
|
+
const keywords = [
|
|
98
|
+
"react",
|
|
99
|
+
"next",
|
|
100
|
+
"nextjs",
|
|
101
|
+
"angular",
|
|
102
|
+
"vue",
|
|
103
|
+
"svelte",
|
|
104
|
+
"typescript",
|
|
105
|
+
"javascript",
|
|
106
|
+
"python",
|
|
107
|
+
"go",
|
|
108
|
+
"rust",
|
|
109
|
+
"java",
|
|
110
|
+
"elixir",
|
|
111
|
+
"tailwind",
|
|
112
|
+
"css",
|
|
113
|
+
"styling",
|
|
114
|
+
"testing",
|
|
115
|
+
"test",
|
|
116
|
+
"e2e",
|
|
117
|
+
"unit",
|
|
118
|
+
"api",
|
|
119
|
+
"rest",
|
|
120
|
+
"graphql",
|
|
121
|
+
"ai",
|
|
122
|
+
"llm",
|
|
123
|
+
"ml",
|
|
124
|
+
"agent",
|
|
125
|
+
"security",
|
|
126
|
+
"auth",
|
|
127
|
+
"database",
|
|
128
|
+
"sql",
|
|
129
|
+
"nosql",
|
|
130
|
+
"docker",
|
|
131
|
+
"kubernetes",
|
|
132
|
+
"devops",
|
|
133
|
+
"ci",
|
|
134
|
+
"cd",
|
|
135
|
+
"state",
|
|
136
|
+
"management",
|
|
137
|
+
"store",
|
|
138
|
+
"validation",
|
|
139
|
+
"schema",
|
|
140
|
+
"zod",
|
|
141
|
+
];
|
|
142
|
+
const lower = description.toLowerCase();
|
|
143
|
+
const found = keywords.filter((kw) => lower.includes(kw));
|
|
144
|
+
return found.slice(0, 10); // Max 10 tags per plugin spec
|
|
145
|
+
}
|
|
146
|
+
//# sourceMappingURL=skill-publish.js.map
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { Stack } from "../types/index.js";
|
|
2
|
+
/**
|
|
3
|
+
* Maps detected project signals to recommended AI skills.
|
|
4
|
+
* Each key is a detection signal (file/dependency/pattern),
|
|
5
|
+
* and the value is the list of skills it implies.
|
|
6
|
+
*/
|
|
7
|
+
export declare const SIGNAL_SKILL_MAP: Record<string, string[]>;
|
|
8
|
+
/**
|
|
9
|
+
* Docker-related files that indicate a containerized project.
|
|
10
|
+
* Used by the init command to suggest Docker zero-downtime deploy scaffolding.
|
|
11
|
+
*/
|
|
12
|
+
export declare const DOCKER_FILES: string[];
|
|
13
|
+
/**
|
|
14
|
+
* Detect whether the project uses Docker (Dockerfile or compose file present).
|
|
15
|
+
*/
|
|
16
|
+
export declare function detectDockerPresence(projectDir: string): Promise<boolean>;
|
|
17
|
+
export interface StackDetectionResult {
|
|
18
|
+
/** Primary stack type (node, python, etc.) */
|
|
19
|
+
stack: Stack | null;
|
|
20
|
+
/** All detected signals with their source */
|
|
21
|
+
signals: DetectedSignal[];
|
|
22
|
+
/** De-duplicated list of recommended skill names */
|
|
23
|
+
recommendedSkills: string[];
|
|
24
|
+
}
|
|
25
|
+
export interface DetectedSignal {
|
|
26
|
+
/** What was detected (e.g., "react", "tailwindcss", "tsconfig.json") */
|
|
27
|
+
signal: string;
|
|
28
|
+
/** Where it was found (e.g., "package.json dependencies", "file exists") */
|
|
29
|
+
source: string;
|
|
30
|
+
/** Skills this signal maps to */
|
|
31
|
+
skills: string[];
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Scan a project directory and detect its tech stack + recommended skills.
|
|
35
|
+
* Reads package.json deps, pyproject.toml, and well-known config files.
|
|
36
|
+
*/
|
|
37
|
+
export declare function detectProjectStack(projectDir: string): Promise<StackDetectionResult>;
|
|
38
|
+
//# sourceMappingURL=stack-detector.d.ts.map
|