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/context.js
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
|
-
import
|
|
1
|
+
import fs from "fs-extra";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { STACK_CONTEXT_MAP } from "../constants.js";
|
|
2
4
|
// =============================================================================
|
|
3
5
|
// Internal helpers
|
|
4
6
|
// =============================================================================
|
|
5
7
|
function getStackContext(stack) {
|
|
6
|
-
return STACK_CONTEXT_MAP[stack] ?? STACK_CONTEXT_MAP[
|
|
8
|
+
return STACK_CONTEXT_MAP[stack] ?? STACK_CONTEXT_MAP["default"];
|
|
7
9
|
}
|
|
8
10
|
export function buildIndexMd(projectName, stackCtx, ciProvider, memory) {
|
|
9
|
-
return `# ${projectName} —
|
|
11
|
+
return `# ${projectName} — Project Index
|
|
10
12
|
|
|
11
|
-
##
|
|
13
|
+
## Structure
|
|
12
14
|
|
|
13
15
|
\`\`\`
|
|
14
16
|
${stackCtx.tree}
|
|
@@ -20,13 +22,14 @@ ${stackCtx.tree}
|
|
|
20
22
|
|
|
21
23
|
## Conventions
|
|
22
24
|
|
|
23
|
-
- **Stack
|
|
25
|
+
- **Stack**: ${stackCtx.conventions}
|
|
24
26
|
- **CI**: ${ciProvider}
|
|
25
27
|
- **Memory**: ${memory}
|
|
26
28
|
`;
|
|
27
29
|
}
|
|
28
|
-
export function buildSummaryMd(projectName, stack, ciProvider, memory, modules) {
|
|
29
|
-
const modulesList = modules.length > 0 ? modules.join(
|
|
30
|
+
export function buildSummaryMd(projectName, stack, ciProvider, memory, modules, dependencies = []) {
|
|
31
|
+
const modulesList = modules.length > 0 ? modules.join(", ") : "none";
|
|
32
|
+
const depsList = dependencies.length > 0 ? dependencies.join(", ") : "none detected";
|
|
30
33
|
return `# ${projectName}
|
|
31
34
|
|
|
32
35
|
## Overview
|
|
@@ -35,10 +38,11 @@ ${stack}-based project scaffolded with javi-forge.
|
|
|
35
38
|
|
|
36
39
|
## Stack
|
|
37
40
|
|
|
38
|
-
- **
|
|
41
|
+
- **Runtime**: ${stack}
|
|
39
42
|
- **CI**: ${ciProvider}
|
|
40
43
|
- **Memory**: ${memory}
|
|
41
44
|
- **Modules**: ${modulesList}
|
|
45
|
+
- **Dependencies**: ${depsList}
|
|
42
46
|
|
|
43
47
|
## Key Decisions
|
|
44
48
|
|
|
@@ -47,6 +51,99 @@ ${stack}-based project scaffolded with javi-forge.
|
|
|
47
51
|
`;
|
|
48
52
|
}
|
|
49
53
|
// =============================================================================
|
|
54
|
+
// Dependency detection
|
|
55
|
+
// =============================================================================
|
|
56
|
+
/**
|
|
57
|
+
* Detect top-level dependencies from project manifest files.
|
|
58
|
+
* Returns up to 10 dependency names (key deps only, not devDeps).
|
|
59
|
+
*/
|
|
60
|
+
export async function detectDependencies(projectDir, stack) {
|
|
61
|
+
const MAX_DEPS = 10;
|
|
62
|
+
try {
|
|
63
|
+
switch (stack) {
|
|
64
|
+
case "node": {
|
|
65
|
+
const pkgPath = path.join(projectDir, "package.json");
|
|
66
|
+
if (!(await fs.pathExists(pkgPath)))
|
|
67
|
+
return [];
|
|
68
|
+
const pkg = await fs.readJson(pkgPath).catch(() => ({}));
|
|
69
|
+
const deps = Object.keys(pkg.dependencies ?? {});
|
|
70
|
+
return deps.slice(0, MAX_DEPS);
|
|
71
|
+
}
|
|
72
|
+
case "python": {
|
|
73
|
+
const pyprojectPath = path.join(projectDir, "pyproject.toml");
|
|
74
|
+
if (await fs.pathExists(pyprojectPath)) {
|
|
75
|
+
const content = await fs.readFile(pyprojectPath, "utf-8");
|
|
76
|
+
const match = content.match(/dependencies\s*=\s*\[([\s\S]*?)\]/);
|
|
77
|
+
if (match?.[1]) {
|
|
78
|
+
const deps = match[1]
|
|
79
|
+
.split("\n")
|
|
80
|
+
.map((l) => l.replace(/[",]/g, "").trim())
|
|
81
|
+
.filter((l) => l.length > 0 && !l.startsWith("#"))
|
|
82
|
+
.map((l) => l.split(/[>=<~!]/)[0].trim())
|
|
83
|
+
.filter(Boolean);
|
|
84
|
+
return deps.slice(0, MAX_DEPS);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
const reqPath = path.join(projectDir, "requirements.txt");
|
|
88
|
+
if (await fs.pathExists(reqPath)) {
|
|
89
|
+
const content = await fs.readFile(reqPath, "utf-8");
|
|
90
|
+
const deps = content
|
|
91
|
+
.split("\n")
|
|
92
|
+
.map((l) => l.trim())
|
|
93
|
+
.filter((l) => l.length > 0 && !l.startsWith("#") && !l.startsWith("-"))
|
|
94
|
+
.map((l) => l.split(/[>=<~!]/)[0].trim())
|
|
95
|
+
.filter(Boolean);
|
|
96
|
+
return deps.slice(0, MAX_DEPS);
|
|
97
|
+
}
|
|
98
|
+
return [];
|
|
99
|
+
}
|
|
100
|
+
case "go": {
|
|
101
|
+
const goModPath = path.join(projectDir, "go.mod");
|
|
102
|
+
if (!(await fs.pathExists(goModPath)))
|
|
103
|
+
return [];
|
|
104
|
+
const content = await fs.readFile(goModPath, "utf-8");
|
|
105
|
+
const requireBlock = content.match(/require\s*\(([\s\S]*?)\)/);
|
|
106
|
+
if (requireBlock?.[1]) {
|
|
107
|
+
const deps = requireBlock[1]
|
|
108
|
+
.split("\n")
|
|
109
|
+
.map((l) => l.trim())
|
|
110
|
+
.filter((l) => l.length > 0 && !l.startsWith("//"))
|
|
111
|
+
.map((l) => {
|
|
112
|
+
const parts = l.split(/\s+/);
|
|
113
|
+
const mod = parts[0] ?? "";
|
|
114
|
+
return mod.split("/").pop() ?? mod;
|
|
115
|
+
})
|
|
116
|
+
.filter(Boolean);
|
|
117
|
+
return deps.slice(0, MAX_DEPS);
|
|
118
|
+
}
|
|
119
|
+
return [];
|
|
120
|
+
}
|
|
121
|
+
case "rust": {
|
|
122
|
+
const cargoPath = path.join(projectDir, "Cargo.toml");
|
|
123
|
+
if (!(await fs.pathExists(cargoPath)))
|
|
124
|
+
return [];
|
|
125
|
+
const content = await fs.readFile(cargoPath, "utf-8");
|
|
126
|
+
const depsSection = content.match(/\[dependencies\]([\s\S]*?)(?=\n\[|$)/);
|
|
127
|
+
if (depsSection?.[1]) {
|
|
128
|
+
const deps = depsSection[1]
|
|
129
|
+
.split("\n")
|
|
130
|
+
.map((l) => l.trim())
|
|
131
|
+
.filter((l) => l.length > 0 && !l.startsWith("#"))
|
|
132
|
+
.map((l) => l.split(/\s*=/)[0].trim())
|
|
133
|
+
.filter(Boolean);
|
|
134
|
+
return deps.slice(0, MAX_DEPS);
|
|
135
|
+
}
|
|
136
|
+
return [];
|
|
137
|
+
}
|
|
138
|
+
default:
|
|
139
|
+
return [];
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
return [];
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
// =============================================================================
|
|
50
147
|
// Public API
|
|
51
148
|
// =============================================================================
|
|
52
149
|
/**
|
|
@@ -59,17 +156,49 @@ export async function generateContextDir(options) {
|
|
|
59
156
|
// Collect enabled modules for summary
|
|
60
157
|
const modules = [];
|
|
61
158
|
if (options.aiSync)
|
|
62
|
-
modules.push(
|
|
159
|
+
modules.push("ai-sync");
|
|
63
160
|
if (options.sdd)
|
|
64
|
-
modules.push(
|
|
161
|
+
modules.push("sdd");
|
|
65
162
|
if (options.ghagga)
|
|
66
|
-
modules.push(
|
|
163
|
+
modules.push("ghagga");
|
|
67
164
|
if (options.mock)
|
|
68
|
-
modules.push(
|
|
165
|
+
modules.push("mock");
|
|
69
166
|
if (options.contextDir)
|
|
70
|
-
modules.push(
|
|
167
|
+
modules.push("context");
|
|
71
168
|
const index = buildIndexMd(projectName, stackCtx, ciProvider, memory);
|
|
72
169
|
const summary = buildSummaryMd(projectName, stack, ciProvider, memory, modules);
|
|
73
170
|
return { index, summary };
|
|
74
171
|
}
|
|
172
|
+
/**
|
|
173
|
+
* Refresh .context/ directory from the forge manifest and live project state.
|
|
174
|
+
* Reads manifest, detects current dependencies, regenerates INDEX.md + summary.md.
|
|
175
|
+
* Returns null if the project is not forge-managed or has no .context/ dir.
|
|
176
|
+
*/
|
|
177
|
+
export async function refreshContextDir(projectDir) {
|
|
178
|
+
const manifestPath = path.join(projectDir, ".javi-forge", "manifest.json");
|
|
179
|
+
const contextDirPath = path.join(projectDir, ".context");
|
|
180
|
+
// Only refresh if forge-managed and .context/ exists
|
|
181
|
+
if (!(await fs.pathExists(manifestPath)) ||
|
|
182
|
+
!(await fs.pathExists(contextDirPath))) {
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
let manifest;
|
|
186
|
+
try {
|
|
187
|
+
manifest = (await fs.readJson(manifestPath));
|
|
188
|
+
}
|
|
189
|
+
catch {
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
const stackCtx = getStackContext(manifest.stack);
|
|
193
|
+
const dependencies = await detectDependencies(projectDir, manifest.stack);
|
|
194
|
+
const index = buildIndexMd(manifest.projectName, stackCtx, manifest.ciProvider, manifest.memory);
|
|
195
|
+
const summary = buildSummaryMd(manifest.projectName, manifest.stack, manifest.ciProvider, manifest.memory, manifest.modules, dependencies);
|
|
196
|
+
// Write updated files
|
|
197
|
+
await fs.writeFile(path.join(contextDirPath, "INDEX.md"), index, "utf-8");
|
|
198
|
+
await fs.writeFile(path.join(contextDirPath, "summary.md"), summary, "utf-8");
|
|
199
|
+
// Update manifest timestamp
|
|
200
|
+
manifest.updatedAt = new Date().toISOString();
|
|
201
|
+
await fs.writeJson(manifestPath, manifest, { spaces: 2 });
|
|
202
|
+
return { index, summary, updated: true };
|
|
203
|
+
}
|
|
75
204
|
//# sourceMappingURL=context.js.map
|
package/dist/lib/docker.d.ts
CHANGED
package/dist/lib/docker.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { execFile, spawn } from
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import
|
|
1
|
+
import { execFile, spawn } from "child_process";
|
|
2
|
+
import crypto from "crypto";
|
|
3
|
+
import fs from "fs-extra";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import { promisify } from "util";
|
|
6
6
|
const execFileAsync = promisify(execFile);
|
|
7
7
|
// =============================================================================
|
|
8
8
|
// Image name
|
|
@@ -15,66 +15,66 @@ export function getImageName(stack) {
|
|
|
15
15
|
// =============================================================================
|
|
16
16
|
export function getDockerfileContent(stack) {
|
|
17
17
|
switch (stack) {
|
|
18
|
-
case
|
|
19
|
-
case
|
|
18
|
+
case "java-gradle":
|
|
19
|
+
case "java-maven":
|
|
20
20
|
return [
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
21
|
+
"ARG JAVA_VERSION=21",
|
|
22
|
+
"FROM eclipse-temurin:${JAVA_VERSION}-jdk-noble",
|
|
23
|
+
"RUN apt-get update && apt-get install -y git curl unzip && rm -rf /var/lib/apt/lists/*",
|
|
24
|
+
"RUN useradd -m -s /bin/bash runner",
|
|
25
|
+
"USER runner",
|
|
26
|
+
"WORKDIR /home/runner/work",
|
|
27
27
|
'ENTRYPOINT ["/bin/bash", "-c"]',
|
|
28
|
-
].join(
|
|
29
|
-
case
|
|
28
|
+
].join("\n");
|
|
29
|
+
case "node":
|
|
30
30
|
return [
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
31
|
+
"FROM node:22-slim",
|
|
32
|
+
"RUN apt-get update && apt-get install -y git && rm -rf /var/lib/apt/lists/*",
|
|
33
|
+
"RUN npm install -g pnpm",
|
|
34
|
+
"RUN useradd -m -s /bin/bash runner",
|
|
35
|
+
"USER runner",
|
|
36
|
+
"WORKDIR /home/runner/work",
|
|
37
37
|
'ENTRYPOINT ["/bin/bash", "-c"]',
|
|
38
|
-
].join(
|
|
39
|
-
case
|
|
38
|
+
].join("\n");
|
|
39
|
+
case "python":
|
|
40
40
|
return [
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
41
|
+
"FROM python:3.12-slim",
|
|
42
|
+
"RUN apt-get update && apt-get install -y git && rm -rf /var/lib/apt/lists/*",
|
|
43
|
+
"RUN pip install --no-cache-dir pytest ruff pylint poetry",
|
|
44
|
+
"RUN useradd -m -s /bin/bash runner",
|
|
45
|
+
"USER runner",
|
|
46
|
+
"WORKDIR /home/runner/work",
|
|
47
47
|
'ENTRYPOINT ["/bin/bash", "-c"]',
|
|
48
|
-
].join(
|
|
49
|
-
case
|
|
48
|
+
].join("\n");
|
|
49
|
+
case "go":
|
|
50
50
|
return [
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
51
|
+
"FROM golang:1.23-bookworm",
|
|
52
|
+
"RUN apt-get update && apt-get install -y git && rm -rf /var/lib/apt/lists/*",
|
|
53
|
+
"RUN go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.62.0 && mv /root/go/bin/golangci-lint /usr/local/bin/",
|
|
54
|
+
"RUN useradd -m -s /bin/bash runner",
|
|
55
|
+
"USER runner",
|
|
56
|
+
"WORKDIR /home/runner/work",
|
|
57
57
|
'ENTRYPOINT ["/bin/bash", "-c"]',
|
|
58
|
-
].join(
|
|
59
|
-
case
|
|
58
|
+
].join("\n");
|
|
59
|
+
case "rust":
|
|
60
60
|
return [
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
61
|
+
"FROM rust:1.83-slim",
|
|
62
|
+
"RUN apt-get update && apt-get install -y git pkg-config libssl-dev && rm -rf /var/lib/apt/lists/*",
|
|
63
|
+
"RUN rustup component add clippy rustfmt",
|
|
64
|
+
"RUN useradd -m -s /bin/bash runner",
|
|
65
|
+
"USER runner",
|
|
66
|
+
"WORKDIR /home/runner/work",
|
|
67
67
|
'ENTRYPOINT ["/bin/bash", "-c"]',
|
|
68
|
-
].join(
|
|
68
|
+
].join("\n");
|
|
69
69
|
default:
|
|
70
70
|
return [
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
71
|
+
"FROM ubuntu:24.04",
|
|
72
|
+
"RUN apt-get update && apt-get install -y git curl && rm -rf /var/lib/apt/lists/*",
|
|
73
|
+
"RUN useradd -m -s /bin/bash runner",
|
|
74
|
+
"USER runner",
|
|
75
|
+
"WORKDIR /home/runner/work",
|
|
76
76
|
'ENTRYPOINT ["/bin/bash", "-c"]',
|
|
77
|
-
].join(
|
|
77
|
+
].join("\n");
|
|
78
78
|
}
|
|
79
79
|
}
|
|
80
80
|
// =============================================================================
|
|
@@ -82,7 +82,7 @@ export function getDockerfileContent(stack) {
|
|
|
82
82
|
// =============================================================================
|
|
83
83
|
export async function isDockerAvailable() {
|
|
84
84
|
try {
|
|
85
|
-
await execFileAsync(
|
|
85
|
+
await execFileAsync("docker", ["info"], { timeout: 5000 });
|
|
86
86
|
return true;
|
|
87
87
|
}
|
|
88
88
|
catch {
|
|
@@ -101,20 +101,24 @@ export async function ensureImage(options) {
|
|
|
101
101
|
const { stack, javaVersion, dockerfilesDir } = options;
|
|
102
102
|
const imageName = getImageName(stack);
|
|
103
103
|
// Resolve Dockerfile path
|
|
104
|
-
const dockerDir = dockerfilesDir ??
|
|
104
|
+
const dockerDir = dockerfilesDir ??
|
|
105
|
+
path.join(path.dirname(new URL(import.meta.url).pathname), "../../ci-local/docker");
|
|
105
106
|
const dockerfilePath = path.join(dockerDir, `${stack}.Dockerfile`);
|
|
106
107
|
// Write Dockerfile if it doesn't exist yet (first run)
|
|
107
|
-
if (!await fs.pathExists(dockerfilePath)) {
|
|
108
|
+
if (!(await fs.pathExists(dockerfilePath))) {
|
|
108
109
|
await fs.ensureDir(dockerDir);
|
|
109
|
-
await fs.writeFile(dockerfilePath, getDockerfileContent(stack),
|
|
110
|
+
await fs.writeFile(dockerfilePath, getDockerfileContent(stack), "utf-8");
|
|
110
111
|
}
|
|
111
112
|
// Staleness check: compare Dockerfile hash with the one embedded in the image label
|
|
112
|
-
const content = await fs.readFile(dockerfilePath,
|
|
113
|
-
const currentHash = crypto.createHash(
|
|
114
|
-
let imageHash =
|
|
113
|
+
const content = await fs.readFile(dockerfilePath, "utf-8");
|
|
114
|
+
const currentHash = crypto.createHash("sha256").update(content).digest("hex");
|
|
115
|
+
let imageHash = "";
|
|
115
116
|
try {
|
|
116
|
-
const { stdout } = await execFileAsync(
|
|
117
|
-
|
|
117
|
+
const { stdout } = await execFileAsync("docker", [
|
|
118
|
+
"inspect",
|
|
119
|
+
"--format",
|
|
120
|
+
'{{index .Config.Labels "dockerfile-hash"}}',
|
|
121
|
+
imageName,
|
|
118
122
|
]);
|
|
119
123
|
imageHash = stdout.trim();
|
|
120
124
|
}
|
|
@@ -126,19 +130,24 @@ export async function ensureImage(options) {
|
|
|
126
130
|
}
|
|
127
131
|
// Build image
|
|
128
132
|
const buildArgs = [
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
+
"build",
|
|
134
|
+
"--label",
|
|
135
|
+
`dockerfile-hash=${currentHash}`,
|
|
136
|
+
"-f",
|
|
137
|
+
dockerfilePath,
|
|
138
|
+
"-t",
|
|
139
|
+
imageName,
|
|
133
140
|
];
|
|
134
|
-
if (javaVersion && (stack ===
|
|
135
|
-
buildArgs.push(
|
|
141
|
+
if (javaVersion && (stack === "java-gradle" || stack === "java-maven")) {
|
|
142
|
+
buildArgs.push("--build-arg", `JAVA_VERSION=${javaVersion}`);
|
|
136
143
|
}
|
|
137
144
|
buildArgs.push(dockerDir);
|
|
138
145
|
await new Promise((resolve, reject) => {
|
|
139
|
-
const proc = spawn(
|
|
140
|
-
proc.on(
|
|
141
|
-
|
|
146
|
+
const proc = spawn("docker", buildArgs, { stdio: "inherit" });
|
|
147
|
+
proc.on("close", (code) => code === 0
|
|
148
|
+
? resolve()
|
|
149
|
+
: reject(new Error(`docker build exited with code ${code}`)));
|
|
150
|
+
proc.on("error", reject);
|
|
142
151
|
});
|
|
143
152
|
return imageName;
|
|
144
153
|
}
|
|
@@ -156,28 +165,41 @@ export async function runInContainer(options) {
|
|
|
156
165
|
const imageName = getImageName(stack);
|
|
157
166
|
const isInteractive = process.stdin.isTTY && stream;
|
|
158
167
|
const dockerArgs = [
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
168
|
+
"run",
|
|
169
|
+
"--rm",
|
|
170
|
+
...(isInteractive ? ["-it"] : []),
|
|
171
|
+
"--stop-timeout",
|
|
172
|
+
"30",
|
|
173
|
+
"--entrypoint",
|
|
174
|
+
"",
|
|
175
|
+
...(user ? ["--user", user] : []),
|
|
176
|
+
"-v",
|
|
177
|
+
`${projectDir}:/home/runner/work`,
|
|
178
|
+
"-e",
|
|
179
|
+
"CI=true",
|
|
166
180
|
imageName,
|
|
167
|
-
|
|
181
|
+
"timeout",
|
|
182
|
+
String(timeout),
|
|
183
|
+
"bash",
|
|
184
|
+
"-c",
|
|
185
|
+
command,
|
|
168
186
|
];
|
|
169
187
|
return new Promise((resolve, reject) => {
|
|
170
|
-
const proc = spawn(
|
|
171
|
-
stdio: stream ?
|
|
188
|
+
const proc = spawn("docker", dockerArgs, {
|
|
189
|
+
stdio: stream ? "inherit" : "pipe",
|
|
172
190
|
});
|
|
173
|
-
let stdout =
|
|
174
|
-
let stderr =
|
|
191
|
+
let stdout = "";
|
|
192
|
+
let stderr = "";
|
|
175
193
|
if (!stream) {
|
|
176
|
-
proc.stdout?.on(
|
|
177
|
-
|
|
194
|
+
proc.stdout?.on("data", (d) => {
|
|
195
|
+
stdout += d.toString();
|
|
196
|
+
});
|
|
197
|
+
proc.stderr?.on("data", (d) => {
|
|
198
|
+
stderr += d.toString();
|
|
199
|
+
});
|
|
178
200
|
}
|
|
179
|
-
proc.on(
|
|
180
|
-
proc.on(
|
|
201
|
+
proc.on("close", (code) => resolve({ exitCode: code ?? 1, stdout, stderr }));
|
|
202
|
+
proc.on("error", reject);
|
|
181
203
|
});
|
|
182
204
|
}
|
|
183
205
|
/**
|
|
@@ -187,38 +209,45 @@ export async function openShell(projectDir) {
|
|
|
187
209
|
const stack = await detectStackFromDir(projectDir);
|
|
188
210
|
const imageName = getImageName(stack);
|
|
189
211
|
await new Promise((resolve, reject) => {
|
|
190
|
-
const proc = spawn(
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
212
|
+
const proc = spawn("docker", [
|
|
213
|
+
"run",
|
|
214
|
+
"--rm",
|
|
215
|
+
"-it",
|
|
216
|
+
"--entrypoint",
|
|
217
|
+
"",
|
|
218
|
+
"-v",
|
|
219
|
+
`${projectDir}:/home/runner/work`,
|
|
220
|
+
"-e",
|
|
221
|
+
"CI=true",
|
|
195
222
|
imageName,
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
223
|
+
"bash",
|
|
224
|
+
"-c",
|
|
225
|
+
"cd /home/runner/work && exec bash",
|
|
226
|
+
], { stdio: "inherit" });
|
|
227
|
+
proc.on("close", () => resolve());
|
|
228
|
+
proc.on("error", reject);
|
|
200
229
|
});
|
|
201
230
|
}
|
|
202
231
|
// =============================================================================
|
|
203
232
|
// Internal helpers
|
|
204
233
|
// =============================================================================
|
|
205
234
|
async function detectStackFromDir(projectDir) {
|
|
206
|
-
if (await fs.pathExists(path.join(projectDir,
|
|
207
|
-
return
|
|
208
|
-
if (await fs.pathExists(path.join(projectDir,
|
|
209
|
-
return
|
|
210
|
-
if (await fs.pathExists(path.join(projectDir,
|
|
211
|
-
return
|
|
212
|
-
if (await fs.pathExists(path.join(projectDir,
|
|
213
|
-
return
|
|
214
|
-
if (await fs.pathExists(path.join(projectDir,
|
|
215
|
-
return
|
|
216
|
-
if (await fs.pathExists(path.join(projectDir,
|
|
217
|
-
return
|
|
218
|
-
if (await fs.pathExists(path.join(projectDir,
|
|
219
|
-
await fs.pathExists(path.join(projectDir,
|
|
220
|
-
await fs.pathExists(path.join(projectDir,
|
|
221
|
-
return
|
|
222
|
-
return
|
|
235
|
+
if (await fs.pathExists(path.join(projectDir, "build.gradle.kts")))
|
|
236
|
+
return "java-gradle";
|
|
237
|
+
if (await fs.pathExists(path.join(projectDir, "build.gradle")))
|
|
238
|
+
return "java-gradle";
|
|
239
|
+
if (await fs.pathExists(path.join(projectDir, "pom.xml")))
|
|
240
|
+
return "java-maven";
|
|
241
|
+
if (await fs.pathExists(path.join(projectDir, "package.json")))
|
|
242
|
+
return "node";
|
|
243
|
+
if (await fs.pathExists(path.join(projectDir, "go.mod")))
|
|
244
|
+
return "go";
|
|
245
|
+
if (await fs.pathExists(path.join(projectDir, "Cargo.toml")))
|
|
246
|
+
return "rust";
|
|
247
|
+
if ((await fs.pathExists(path.join(projectDir, "pyproject.toml"))) ||
|
|
248
|
+
(await fs.pathExists(path.join(projectDir, "requirements.txt"))) ||
|
|
249
|
+
(await fs.pathExists(path.join(projectDir, "setup.py"))))
|
|
250
|
+
return "python";
|
|
251
|
+
return "node"; // fallback
|
|
223
252
|
}
|
|
224
253
|
//# sourceMappingURL=docker.js.map
|
|
@@ -14,5 +14,5 @@ export interface ValidationError {
|
|
|
14
14
|
/**
|
|
15
15
|
* Validate frontmatter against schema rules for agent or skill definitions.
|
|
16
16
|
*/
|
|
17
|
-
export declare function validateFrontmatter(frontmatter: Record<string, unknown>, type:
|
|
17
|
+
export declare function validateFrontmatter(frontmatter: Record<string, unknown>, type: "agent" | "skill"): ValidationError[];
|
|
18
18
|
//# sourceMappingURL=frontmatter.d.ts.map
|
package/dist/lib/frontmatter.js
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
import YAML from
|
|
1
|
+
import YAML from "yaml";
|
|
2
2
|
/**
|
|
3
3
|
* Extract YAML frontmatter from a markdown string.
|
|
4
4
|
* Frontmatter is delimited by --- at the start of the file.
|
|
5
5
|
*/
|
|
6
6
|
export function parseFrontmatter(raw) {
|
|
7
7
|
const trimmed = raw.trimStart();
|
|
8
|
-
if (!trimmed.startsWith(
|
|
8
|
+
if (!trimmed.startsWith("---"))
|
|
9
9
|
return null;
|
|
10
|
-
const endIdx = trimmed.indexOf(
|
|
10
|
+
const endIdx = trimmed.indexOf("---", 3);
|
|
11
11
|
if (endIdx === -1)
|
|
12
12
|
return null;
|
|
13
13
|
const yamlBlock = trimmed.slice(3, endIdx).trim();
|
|
14
14
|
const content = trimmed.slice(endIdx + 3).trim();
|
|
15
15
|
try {
|
|
16
16
|
const data = YAML.parse(yamlBlock);
|
|
17
|
-
if (typeof data !==
|
|
17
|
+
if (typeof data !== "object" || data === null)
|
|
18
18
|
return null;
|
|
19
19
|
return { data, content };
|
|
20
20
|
}
|
|
@@ -29,30 +29,44 @@ const KEBAB_CASE_RE = /^[a-z0-9]+(-[a-z0-9]+)*$/;
|
|
|
29
29
|
export function validateFrontmatter(frontmatter, type) {
|
|
30
30
|
const errors = [];
|
|
31
31
|
// name: required, kebab-case, 2-60 chars
|
|
32
|
-
const name = frontmatter[
|
|
33
|
-
if (typeof name !==
|
|
34
|
-
errors.push({
|
|
32
|
+
const name = frontmatter["name"];
|
|
33
|
+
if (typeof name !== "string" || !name) {
|
|
34
|
+
errors.push({
|
|
35
|
+
field: "name",
|
|
36
|
+
message: "name is required and must be a string",
|
|
37
|
+
});
|
|
35
38
|
}
|
|
36
39
|
else {
|
|
37
40
|
if (name.length < 2 || name.length > 60) {
|
|
38
|
-
errors.push({ field:
|
|
41
|
+
errors.push({ field: "name", message: "name must be 2-60 characters" });
|
|
39
42
|
}
|
|
40
43
|
if (!KEBAB_CASE_RE.test(name)) {
|
|
41
|
-
errors.push({
|
|
44
|
+
errors.push({
|
|
45
|
+
field: "name",
|
|
46
|
+
message: "name must be kebab-case (e.g. my-skill-name)",
|
|
47
|
+
});
|
|
42
48
|
}
|
|
43
49
|
}
|
|
44
50
|
// description: required, non-empty, min 10 chars
|
|
45
|
-
const desc = frontmatter[
|
|
46
|
-
if (typeof desc !==
|
|
47
|
-
errors.push({
|
|
51
|
+
const desc = frontmatter["description"];
|
|
52
|
+
if (typeof desc !== "string" || !desc) {
|
|
53
|
+
errors.push({
|
|
54
|
+
field: "description",
|
|
55
|
+
message: "description is required and must be a string",
|
|
56
|
+
});
|
|
48
57
|
}
|
|
49
58
|
else if (desc.length < 10) {
|
|
50
|
-
errors.push({
|
|
59
|
+
errors.push({
|
|
60
|
+
field: "description",
|
|
61
|
+
message: "description must be at least 10 characters",
|
|
62
|
+
});
|
|
51
63
|
}
|
|
52
64
|
// skill-specific: description should include "Trigger:" hint
|
|
53
|
-
if (type ===
|
|
65
|
+
if (type === "skill" &&
|
|
66
|
+
typeof desc === "string" &&
|
|
67
|
+
!desc.includes("Trigger:")) {
|
|
54
68
|
errors.push({
|
|
55
|
-
field:
|
|
69
|
+
field: "description",
|
|
56
70
|
message: 'skill description should include a "Trigger:" hint for auto-invoke',
|
|
57
71
|
});
|
|
58
72
|
}
|