javi-forge 1.3.0 → 1.4.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/ci-local/ci-local.sh +9 -1
- package/ci-local/hooks/commit-msg +0 -0
- package/ci-local/hooks/pre-commit +1 -1
- package/ci-local/hooks/pre-push +0 -0
- package/ci-local/install.sh +0 -0
- package/ci-local/lib/common.sh +183 -0
- package/dist/__integration__/helpers.d.ts +20 -0
- package/dist/__integration__/helpers.d.ts.map +1 -0
- package/dist/__integration__/helpers.js +31 -0
- package/dist/__integration__/helpers.js.map +1 -0
- package/dist/commands/analyze.d.ts.map +1 -0
- package/dist/commands/analyze.js.map +1 -0
- package/dist/commands/ci.d.ts.map +1 -0
- package/dist/commands/ci.js +13 -8
- package/dist/commands/ci.js.map +1 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +1 -3
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +14 -6
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/llmstxt.d.ts.map +1 -0
- package/dist/commands/llmstxt.js.map +1 -0
- package/dist/commands/plugin.d.ts.map +1 -0
- package/dist/commands/plugin.js.map +1 -0
- package/dist/constants.d.ts +0 -4
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +0 -4
- package/dist/constants.js.map +1 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -11
- package/dist/index.js.map +1 -0
- package/dist/lib/common.d.ts.map +1 -0
- package/dist/lib/common.js.map +1 -0
- package/dist/lib/docker.d.ts +2 -0
- package/dist/lib/docker.d.ts.map +1 -0
- package/dist/lib/docker.js +2 -1
- package/dist/lib/docker.js.map +1 -0
- package/dist/lib/frontmatter.d.ts.map +1 -0
- package/dist/lib/frontmatter.js.map +1 -0
- package/dist/lib/plugin.d.ts.map +1 -0
- package/dist/lib/plugin.js.map +1 -0
- package/dist/lib/template.d.ts.map +1 -0
- package/dist/lib/template.js.map +1 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js.map +1 -0
- package/dist/ui/AnalyzeUI.d.ts.map +1 -0
- package/dist/ui/AnalyzeUI.js.map +1 -0
- package/dist/ui/App.d.ts.map +1 -0
- package/dist/ui/App.js.map +1 -0
- package/dist/ui/CI.d.ts.map +1 -0
- package/dist/ui/CI.js.map +1 -0
- package/dist/ui/CIContext.d.ts.map +1 -0
- package/dist/ui/CIContext.js.map +1 -0
- package/dist/ui/CISelector.d.ts.map +1 -0
- package/dist/ui/CISelector.js.map +1 -0
- package/dist/ui/Doctor.d.ts.map +1 -0
- package/dist/ui/Doctor.js +1 -1
- package/dist/ui/Doctor.js.map +1 -0
- package/dist/ui/Header.d.ts.map +1 -0
- package/dist/ui/Header.js.map +1 -0
- package/dist/ui/LlmsTxt.d.ts.map +1 -0
- package/dist/ui/LlmsTxt.js.map +1 -0
- package/dist/ui/MemorySelector.d.ts.map +1 -0
- package/dist/ui/MemorySelector.js.map +1 -0
- package/dist/ui/NameInput.d.ts.map +1 -0
- package/dist/ui/NameInput.js.map +1 -0
- package/dist/ui/OptionSelector.d.ts.map +1 -0
- package/dist/ui/OptionSelector.js +1 -1
- package/dist/ui/OptionSelector.js.map +1 -0
- package/dist/ui/Plugin.d.ts.map +1 -0
- package/dist/ui/Plugin.js.map +1 -0
- package/dist/ui/Progress.d.ts.map +1 -0
- package/dist/ui/Progress.js.map +1 -0
- package/dist/ui/StackSelector.d.ts.map +1 -0
- package/dist/ui/StackSelector.js.map +1 -0
- package/dist/ui/Summary.d.ts.map +1 -0
- package/dist/ui/Summary.js.map +1 -0
- package/dist/ui/Welcome.d.ts.map +1 -0
- package/dist/ui/Welcome.js.map +1 -0
- package/dist/ui/theme.d.ts.map +1 -0
- package/dist/ui/theme.js.map +1 -0
- package/lib/common.sh +2 -2
- package/modules/ghagga/README.md +2 -2
- package/modules/ghagga/setup-ghagga.sh +1 -1
- package/package.json +25 -12
- package/templates/github/ci-go.yml +1 -1
- package/templates/github/ci-java.yml +2 -2
- package/templates/github/ci-node.yml +1 -1
- package/templates/github/ci-python.yml +1 -1
- package/templates/github/ci-rust.yml +1 -1
- package/templates/github/ghagga-review.yml +28 -0
- package/workflows/reusable-build-go.yml +1 -1
- package/workflows/reusable-build-java.yml +1 -1
- package/workflows/reusable-build-node.yml +1 -1
- package/workflows/reusable-build-python.yml +1 -1
- package/workflows/reusable-build-rust.yml +1 -1
- package/workflows/reusable-docker.yml +1 -1
- package/workflows/reusable-ghagga-review.yml +1 -1
- package/workflows/reusable-release.yml +1 -1
- package/.releaserc +0 -45
- package/dist/commands/analyze.test.d.ts +0 -2
- package/dist/commands/doctor.test.d.ts +0 -2
- package/dist/commands/init.test.d.ts +0 -2
- package/dist/commands/llmstxt.test.d.ts +0 -2
- package/dist/commands/plugin.test.d.ts +0 -2
- package/dist/commands/sync.d.ts +0 -8
- package/dist/commands/sync.js +0 -201
- package/dist/e2e/aggressive.e2e.test.d.ts +0 -2
- package/dist/e2e/commands.e2e.test.d.ts +0 -2
- package/dist/lib/common.test.d.ts +0 -2
- package/dist/lib/frontmatter.test.d.ts +0 -2
- package/dist/lib/plugin.test.d.ts +0 -2
- package/dist/lib/template.test.d.ts +0 -2
- package/dist/ui/SyncUI.d.ts +0 -10
- package/dist/ui/SyncUI.js +0 -64
- package/tasks/_TEMPLATE/files-edited.md +0 -3
- package/tasks/_TEMPLATE/plan.md +0 -3
- package/tasks/_TEMPLATE/research.md +0 -3
- package/tasks/_TEMPLATE/verification.md +0 -5
package/package.json
CHANGED
|
@@ -1,11 +1,34 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "javi-forge",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "Project scaffolding and AI-ready CI bootstrap",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"javi-forge": "./dist/index.js"
|
|
8
8
|
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsc",
|
|
11
|
+
"dev": "tsx watch src/index.tsx",
|
|
12
|
+
"start": "node dist/index.js",
|
|
13
|
+
"typecheck": "tsc --noEmit",
|
|
14
|
+
"test": "vitest run",
|
|
15
|
+
"test:watch": "vitest",
|
|
16
|
+
"test:coverage": "vitest run --coverage",
|
|
17
|
+
"test:mutation": "stryker run"
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"dist/",
|
|
21
|
+
"ci-local/",
|
|
22
|
+
"modules/",
|
|
23
|
+
"templates/",
|
|
24
|
+
"workflows/",
|
|
25
|
+
"lib/",
|
|
26
|
+
"!dist/**/*.test.*",
|
|
27
|
+
"!dist/**/__snapshots__",
|
|
28
|
+
"package.json",
|
|
29
|
+
"README.md",
|
|
30
|
+
".gitignore.template"
|
|
31
|
+
],
|
|
9
32
|
"author": "JNZader",
|
|
10
33
|
"license": "MIT",
|
|
11
34
|
"repository": {
|
|
@@ -45,15 +68,5 @@
|
|
|
45
68
|
"tsx": "^4.21.0",
|
|
46
69
|
"typescript": "^5.9.3",
|
|
47
70
|
"vitest": "^4.1.0"
|
|
48
|
-
},
|
|
49
|
-
"scripts": {
|
|
50
|
-
"build": "tsc",
|
|
51
|
-
"dev": "tsx watch src/index.tsx",
|
|
52
|
-
"start": "node dist/index.js",
|
|
53
|
-
"typecheck": "tsc --noEmit",
|
|
54
|
-
"test": "vitest run",
|
|
55
|
-
"test:watch": "vitest",
|
|
56
|
-
"test:coverage": "vitest run --coverage",
|
|
57
|
-
"test:mutation": "stryker run"
|
|
58
71
|
}
|
|
59
|
-
}
|
|
72
|
+
}
|
|
@@ -34,7 +34,7 @@ concurrency:
|
|
|
34
34
|
|
|
35
35
|
jobs:
|
|
36
36
|
build:
|
|
37
|
-
uses: JNZader/
|
|
37
|
+
uses: JNZader/javi-forge/.github/workflows/reusable-build-go.yml@main
|
|
38
38
|
with:
|
|
39
39
|
go-version: '1.23' # Change to: 1.22, 1.23
|
|
40
40
|
run-lint: true
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
# ===========================================
|
|
4
4
|
# Copy to: .github/workflows/ci.yml
|
|
5
5
|
#
|
|
6
|
-
# This uses reusable workflows from
|
|
6
|
+
# This uses reusable workflows from javi-forge.
|
|
7
7
|
# Runs on pushes to main, PRs, and manual triggers.
|
|
8
8
|
# ===========================================
|
|
9
9
|
|
|
@@ -37,7 +37,7 @@ concurrency:
|
|
|
37
37
|
|
|
38
38
|
jobs:
|
|
39
39
|
build:
|
|
40
|
-
uses: JNZader/
|
|
40
|
+
uses: JNZader/javi-forge/.github/workflows/reusable-build-java.yml@main
|
|
41
41
|
with:
|
|
42
42
|
java-version: '21' # Change to your version: 17, 21, 25
|
|
43
43
|
run-spotless: true
|
|
@@ -34,7 +34,7 @@ concurrency:
|
|
|
34
34
|
|
|
35
35
|
jobs:
|
|
36
36
|
build:
|
|
37
|
-
uses: JNZader/
|
|
37
|
+
uses: JNZader/javi-forge/.github/workflows/reusable-build-node.yml@main
|
|
38
38
|
with:
|
|
39
39
|
node-version: '20' # Change to: 18, 20, 22
|
|
40
40
|
package-manager: 'npm' # Change to: npm, yarn, pnpm
|
|
@@ -34,7 +34,7 @@ concurrency:
|
|
|
34
34
|
|
|
35
35
|
jobs:
|
|
36
36
|
build:
|
|
37
|
-
uses: JNZader/
|
|
37
|
+
uses: JNZader/javi-forge/.github/workflows/reusable-build-python.yml@main
|
|
38
38
|
with:
|
|
39
39
|
python-version: '3.12' # Change to: 3.10, 3.11, 3.12
|
|
40
40
|
package-manager: 'pip' # Change to: pip, poetry, uv
|
|
@@ -34,7 +34,7 @@ concurrency:
|
|
|
34
34
|
|
|
35
35
|
jobs:
|
|
36
36
|
build:
|
|
37
|
-
uses: JNZader/
|
|
37
|
+
uses: JNZader/javi-forge/.github/workflows/reusable-build-rust.yml@main
|
|
38
38
|
with:
|
|
39
39
|
toolchain: 'stable' # Change to: stable, beta, nightly
|
|
40
40
|
run-clippy: true
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# ===========================================
|
|
2
|
+
# GHAGGA AI Code Review
|
|
3
|
+
# ===========================================
|
|
4
|
+
# Triggers AI code review on pull requests.
|
|
5
|
+
# Requires: GHAGGA GitHub Action (JNZader/ghagga@v1)
|
|
6
|
+
#
|
|
7
|
+
# Modes: simple (default), workflow, consensus
|
|
8
|
+
# ===========================================
|
|
9
|
+
|
|
10
|
+
name: Code Review
|
|
11
|
+
|
|
12
|
+
on:
|
|
13
|
+
pull_request:
|
|
14
|
+
types: [opened, synchronize, reopened]
|
|
15
|
+
|
|
16
|
+
permissions:
|
|
17
|
+
pull-requests: write
|
|
18
|
+
contents: read
|
|
19
|
+
|
|
20
|
+
jobs:
|
|
21
|
+
review:
|
|
22
|
+
runs-on: ubuntu-latest
|
|
23
|
+
steps:
|
|
24
|
+
- uses: actions/checkout@v4
|
|
25
|
+
- uses: JNZader/ghagga@v1
|
|
26
|
+
with:
|
|
27
|
+
mode: simple
|
|
28
|
+
provider: github
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
# Usage:
|
|
7
7
|
# jobs:
|
|
8
8
|
# build:
|
|
9
|
-
# uses: JNZader/
|
|
9
|
+
# uses: JNZader/javi-forge/.github/workflows/reusable-build-go.yml@main
|
|
10
10
|
# with:
|
|
11
11
|
# go-version: '1.23'
|
|
12
12
|
# ===========================================
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
# Usage in your project:
|
|
7
7
|
# jobs:
|
|
8
8
|
# build:
|
|
9
|
-
# uses: JNZader/
|
|
9
|
+
# uses: JNZader/javi-forge/.github/workflows/reusable-build-java.yml@main
|
|
10
10
|
# with:
|
|
11
11
|
# java-version: '21'
|
|
12
12
|
# ===========================================
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
# Usage:
|
|
7
7
|
# jobs:
|
|
8
8
|
# build:
|
|
9
|
-
# uses: JNZader/
|
|
9
|
+
# uses: JNZader/javi-forge/.github/workflows/reusable-build-node.yml@main
|
|
10
10
|
# with:
|
|
11
11
|
# node-version: '20'
|
|
12
12
|
# ===========================================
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
# Usage:
|
|
7
7
|
# jobs:
|
|
8
8
|
# build:
|
|
9
|
-
# uses: JNZader/
|
|
9
|
+
# uses: JNZader/javi-forge/.github/workflows/reusable-build-python.yml@main
|
|
10
10
|
# with:
|
|
11
11
|
# python-version: '3.12'
|
|
12
12
|
# ===========================================
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
# Usage:
|
|
7
7
|
# jobs:
|
|
8
8
|
# build:
|
|
9
|
-
# uses: JNZader/
|
|
9
|
+
# uses: JNZader/javi-forge/.github/workflows/reusable-build-rust.yml@main
|
|
10
10
|
# ===========================================
|
|
11
11
|
|
|
12
12
|
name: Rust Build (Reusable)
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
# Usage:
|
|
7
7
|
# jobs:
|
|
8
8
|
# docker:
|
|
9
|
-
# uses: JNZader/
|
|
9
|
+
# uses: JNZader/javi-forge/.github/workflows/reusable-docker.yml@main
|
|
10
10
|
# with:
|
|
11
11
|
# dockerfile: './Dockerfile'
|
|
12
12
|
# ===========================================
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
# Usage:
|
|
7
7
|
# jobs:
|
|
8
8
|
# review:
|
|
9
|
-
# uses: JNZader/
|
|
9
|
+
# uses: JNZader/javi-forge/.github/workflows/reusable-ghagga-review.yml@main
|
|
10
10
|
# with:
|
|
11
11
|
# ghagga-url: ${{ vars.GHAGGA_URL }}
|
|
12
12
|
# secrets:
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
# Usage:
|
|
7
7
|
# jobs:
|
|
8
8
|
# release:
|
|
9
|
-
# uses: JNZader/
|
|
9
|
+
# uses: JNZader/javi-forge/.github/workflows/reusable-release.yml@main
|
|
10
10
|
# secrets:
|
|
11
11
|
# token: ${{ secrets.GITHUB_TOKEN }}
|
|
12
12
|
# ===========================================
|
package/.releaserc
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"branches": ["main"],
|
|
3
|
-
"tagFormat": "v${version}",
|
|
4
|
-
"plugins": [
|
|
5
|
-
["@semantic-release/commit-analyzer", {
|
|
6
|
-
"preset": "conventionalcommits",
|
|
7
|
-
"releaseRules": [
|
|
8
|
-
{"type": "feat", "release": "minor"},
|
|
9
|
-
{"type": "fix", "release": "patch"},
|
|
10
|
-
{"type": "perf", "release": "patch"},
|
|
11
|
-
{"type": "refactor", "release": "patch"},
|
|
12
|
-
{"type": "docs", "release": false},
|
|
13
|
-
{"type": "style", "release": false},
|
|
14
|
-
{"type": "chore", "release": false},
|
|
15
|
-
{"type": "test", "release": false},
|
|
16
|
-
{"breaking": true, "release": "major"}
|
|
17
|
-
]
|
|
18
|
-
}],
|
|
19
|
-
["@semantic-release/release-notes-generator", {
|
|
20
|
-
"preset": "conventionalcommits",
|
|
21
|
-
"presetConfig": {
|
|
22
|
-
"types": [
|
|
23
|
-
{"type": "feat", "section": "Features"},
|
|
24
|
-
{"type": "fix", "section": "Bug Fixes"},
|
|
25
|
-
{"type": "perf", "section": "Performance"},
|
|
26
|
-
{"type": "refactor", "section": "Refactoring"},
|
|
27
|
-
{"type": "docs", "section": "Documentation", "hidden": true},
|
|
28
|
-
{"type": "chore", "section": "Maintenance", "hidden": true}
|
|
29
|
-
]
|
|
30
|
-
}
|
|
31
|
-
}],
|
|
32
|
-
["@semantic-release/changelog", {
|
|
33
|
-
"changelogFile": "CHANGELOG.md"
|
|
34
|
-
}],
|
|
35
|
-
["@semantic-release/exec", {
|
|
36
|
-
"prepareCmd": "echo ${nextRelease.version} > .framework-version"
|
|
37
|
-
}],
|
|
38
|
-
"@semantic-release/npm",
|
|
39
|
-
["@semantic-release/git", {
|
|
40
|
-
"assets": ["CHANGELOG.md", "package.json", ".framework-version"],
|
|
41
|
-
"message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
|
|
42
|
-
}],
|
|
43
|
-
"@semantic-release/github"
|
|
44
|
-
]
|
|
45
|
-
}
|
package/dist/commands/sync.d.ts
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import type { SyncOptions, InitStep } from '../types/index.js';
|
|
2
|
-
type StepCallback = (step: InitStep) => void;
|
|
3
|
-
/**
|
|
4
|
-
* Sync AI config to a project directory, generating CLI-specific config files.
|
|
5
|
-
*/
|
|
6
|
-
export declare function syncAIConfig(options: SyncOptions, onStep: StepCallback): Promise<void>;
|
|
7
|
-
export {};
|
|
8
|
-
//# sourceMappingURL=sync.d.ts.map
|
package/dist/commands/sync.js
DELETED
|
@@ -1,201 +0,0 @@
|
|
|
1
|
-
import fs from 'fs-extra';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import { ensureDirExists } from '../lib/common.js';
|
|
4
|
-
import { parseFrontmatter } from '../lib/frontmatter.js';
|
|
5
|
-
import { AI_CONFIG_DIR, AI_CLI_CONFIG_FILES, MARKER_START, MARKER_END, } from '../constants.js';
|
|
6
|
-
const ALL_TARGETS = ['claude', 'opencode', 'gemini', 'qwen', 'codex', 'copilot'];
|
|
7
|
-
function report(onStep, id, label, status, detail) {
|
|
8
|
-
onStep({ id, label, status, detail });
|
|
9
|
-
}
|
|
10
|
-
/** Read .skillignore and parse exclusion rules */
|
|
11
|
-
async function loadSkillIgnore(configDir) {
|
|
12
|
-
const globalExcludes = new Set();
|
|
13
|
-
const targetExcludes = new Map();
|
|
14
|
-
const ignorePath = path.join(configDir, '.skillignore');
|
|
15
|
-
if (!await fs.pathExists(ignorePath)) {
|
|
16
|
-
return { globalExcludes, targetExcludes };
|
|
17
|
-
}
|
|
18
|
-
const content = await fs.readFile(ignorePath, 'utf-8');
|
|
19
|
-
for (const rawLine of content.split('\n')) {
|
|
20
|
-
const line = rawLine.trim();
|
|
21
|
-
if (!line || line.startsWith('#'))
|
|
22
|
-
continue;
|
|
23
|
-
if (line.includes(':')) {
|
|
24
|
-
const [target, skill] = line.split(':', 2);
|
|
25
|
-
if (target && skill) {
|
|
26
|
-
if (!targetExcludes.has(target))
|
|
27
|
-
targetExcludes.set(target, new Set());
|
|
28
|
-
targetExcludes.get(target).add(skill);
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
else {
|
|
32
|
-
globalExcludes.add(line);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
return { globalExcludes, targetExcludes };
|
|
36
|
-
}
|
|
37
|
-
/** Recursively collect agent/skill markdown files from a directory */
|
|
38
|
-
async function collectMarkdownEntries(baseDir, prefix = '') {
|
|
39
|
-
const entries = [];
|
|
40
|
-
if (!await fs.pathExists(baseDir))
|
|
41
|
-
return entries;
|
|
42
|
-
const items = await fs.readdir(baseDir, { withFileTypes: true });
|
|
43
|
-
for (const item of items) {
|
|
44
|
-
if (item.name.startsWith('_') || item.name.startsWith('.'))
|
|
45
|
-
continue;
|
|
46
|
-
const itemPath = path.join(baseDir, item.name);
|
|
47
|
-
const relPath = prefix ? `${prefix}/${item.name}` : item.name;
|
|
48
|
-
if (item.isDirectory()) {
|
|
49
|
-
// Look for SKILL.md or agent markdown inside
|
|
50
|
-
const skillMd = path.join(itemPath, 'SKILL.md');
|
|
51
|
-
if (await fs.pathExists(skillMd)) {
|
|
52
|
-
const content = await fs.readFile(skillMd, 'utf-8');
|
|
53
|
-
entries.push({ relativePath: relPath, name: item.name, content });
|
|
54
|
-
}
|
|
55
|
-
else {
|
|
56
|
-
// Recurse into subcategory directories
|
|
57
|
-
const subEntries = await collectMarkdownEntries(itemPath, relPath);
|
|
58
|
-
entries.push(...subEntries);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
else if (item.name.endsWith('.md') && item.name !== 'README.md') {
|
|
62
|
-
const content = await fs.readFile(itemPath, 'utf-8');
|
|
63
|
-
const name = item.name.replace('.md', '');
|
|
64
|
-
entries.push({ relativePath: relPath, name, content });
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
return entries;
|
|
68
|
-
}
|
|
69
|
-
/** Build a CLAUDE.md / GEMINI.md / etc from agents + skills */
|
|
70
|
-
function buildConfigContent(target, agents, skills) {
|
|
71
|
-
const lines = [];
|
|
72
|
-
lines.push(`# AI Configuration for ${target}`);
|
|
73
|
-
lines.push('');
|
|
74
|
-
lines.push(`Auto-generated by javi-forge sync. Do not edit between markers.`);
|
|
75
|
-
lines.push('');
|
|
76
|
-
if (agents.length > 0) {
|
|
77
|
-
lines.push('## Agents');
|
|
78
|
-
lines.push('');
|
|
79
|
-
for (const agent of agents) {
|
|
80
|
-
const fm = parseFrontmatter(agent.content);
|
|
81
|
-
if (fm?.data) {
|
|
82
|
-
const desc = fm.data['description'] ?? '';
|
|
83
|
-
lines.push(`- **${agent.name}**: ${desc}`);
|
|
84
|
-
}
|
|
85
|
-
else {
|
|
86
|
-
lines.push(`- **${agent.name}**`);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
lines.push('');
|
|
90
|
-
}
|
|
91
|
-
if (skills.length > 0) {
|
|
92
|
-
lines.push('## Available Skills');
|
|
93
|
-
lines.push('');
|
|
94
|
-
for (const skill of skills) {
|
|
95
|
-
const fm = parseFrontmatter(skill.content);
|
|
96
|
-
if (fm?.data) {
|
|
97
|
-
const desc = fm.data['description'] ?? '';
|
|
98
|
-
lines.push(`- **${skill.name}**: ${desc}`);
|
|
99
|
-
}
|
|
100
|
-
else {
|
|
101
|
-
lines.push(`- **${skill.name}**`);
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
lines.push('');
|
|
105
|
-
}
|
|
106
|
-
return lines.join('\n');
|
|
107
|
-
}
|
|
108
|
-
/** Merge generated content into an existing file using markers */
|
|
109
|
-
function mergeWithMarkers(existingContent, generatedContent) {
|
|
110
|
-
const startIdx = existingContent.indexOf(MARKER_START);
|
|
111
|
-
const endIdx = existingContent.indexOf(MARKER_END);
|
|
112
|
-
const markerBlock = `${MARKER_START}\n${generatedContent}\n${MARKER_END}`;
|
|
113
|
-
if (startIdx !== -1 && endIdx !== -1) {
|
|
114
|
-
// Replace existing marker block
|
|
115
|
-
return existingContent.slice(0, startIdx) + markerBlock + existingContent.slice(endIdx + MARKER_END.length);
|
|
116
|
-
}
|
|
117
|
-
// Append marker block
|
|
118
|
-
return existingContent.trimEnd() + '\n\n' + markerBlock + '\n';
|
|
119
|
-
}
|
|
120
|
-
/**
|
|
121
|
-
* Sync AI config to a project directory, generating CLI-specific config files.
|
|
122
|
-
*/
|
|
123
|
-
export async function syncAIConfig(options, onStep) {
|
|
124
|
-
const { target, mode, projectDir, dryRun } = options;
|
|
125
|
-
const targets = target === 'all' ? ALL_TARGETS : [target];
|
|
126
|
-
const configDir = AI_CONFIG_DIR;
|
|
127
|
-
// Load exclusions
|
|
128
|
-
const { globalExcludes, targetExcludes } = await loadSkillIgnore(configDir);
|
|
129
|
-
// Collect agents and skills
|
|
130
|
-
const agentsDir = path.join(configDir, 'agents');
|
|
131
|
-
const skillsDir = path.join(configDir, 'skills');
|
|
132
|
-
const allAgents = await collectMarkdownEntries(agentsDir);
|
|
133
|
-
const allSkills = await collectMarkdownEntries(skillsDir);
|
|
134
|
-
for (const cli of targets) {
|
|
135
|
-
const stepId = `sync-${cli}`;
|
|
136
|
-
report(onStep, stepId, `Sync config for ${cli}`, 'running');
|
|
137
|
-
try {
|
|
138
|
-
// Filter out excluded skills
|
|
139
|
-
const cliExcludes = targetExcludes.get(cli) ?? new Set();
|
|
140
|
-
const filteredSkills = allSkills.filter(s => {
|
|
141
|
-
const nameWithoutExt = s.name;
|
|
142
|
-
const relWithoutExt = s.relativePath.replace(/\.md$/, '').replace(/\/SKILL\.md$/, '');
|
|
143
|
-
return !globalExcludes.has(nameWithoutExt)
|
|
144
|
-
&& !globalExcludes.has(relWithoutExt)
|
|
145
|
-
&& !cliExcludes.has(nameWithoutExt)
|
|
146
|
-
&& !cliExcludes.has(relWithoutExt);
|
|
147
|
-
});
|
|
148
|
-
const filteredAgents = allAgents.filter(a => {
|
|
149
|
-
return !globalExcludes.has(a.name) && !cliExcludes.has(a.name);
|
|
150
|
-
});
|
|
151
|
-
// Build the config content
|
|
152
|
-
const generatedContent = buildConfigContent(cli, filteredAgents, filteredSkills);
|
|
153
|
-
// Determine destination
|
|
154
|
-
const configFileName = AI_CLI_CONFIG_FILES[cli];
|
|
155
|
-
if (!configFileName) {
|
|
156
|
-
report(onStep, stepId, `Sync config for ${cli}`, 'skipped', 'no config file mapping');
|
|
157
|
-
continue;
|
|
158
|
-
}
|
|
159
|
-
const destPath = path.join(projectDir, configFileName);
|
|
160
|
-
if (!dryRun) {
|
|
161
|
-
await ensureDirExists(path.dirname(destPath));
|
|
162
|
-
if (mode === 'merge' && await fs.pathExists(destPath)) {
|
|
163
|
-
const existing = await fs.readFile(destPath, 'utf-8');
|
|
164
|
-
const merged = mergeWithMarkers(existing, generatedContent);
|
|
165
|
-
await fs.writeFile(destPath, merged, 'utf-8');
|
|
166
|
-
}
|
|
167
|
-
else {
|
|
168
|
-
await fs.writeFile(destPath, generatedContent, 'utf-8');
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
report(onStep, stepId, `Sync config for ${cli}`, 'done', `${filteredAgents.length} agents, ${filteredSkills.length} skills → ${configFileName}`);
|
|
172
|
-
// Sync Claude commands if target is claude
|
|
173
|
-
if (cli === 'claude') {
|
|
174
|
-
const cmdStepId = `sync-${cli}-commands`;
|
|
175
|
-
report(onStep, cmdStepId, 'Sync Claude commands', 'running');
|
|
176
|
-
try {
|
|
177
|
-
const commandsSrc = path.join(configDir, 'commands');
|
|
178
|
-
if (await fs.pathExists(commandsSrc)) {
|
|
179
|
-
const commandsDest = path.join(projectDir, '.claude', 'commands');
|
|
180
|
-
if (!dryRun) {
|
|
181
|
-
await ensureDirExists(commandsDest);
|
|
182
|
-
await fs.copy(commandsSrc, commandsDest, { overwrite: true });
|
|
183
|
-
}
|
|
184
|
-
const cmdFiles = await fs.readdir(commandsSrc);
|
|
185
|
-
report(onStep, cmdStepId, 'Sync Claude commands', 'done', `${cmdFiles.length} commands`);
|
|
186
|
-
}
|
|
187
|
-
else {
|
|
188
|
-
report(onStep, cmdStepId, 'Sync Claude commands', 'skipped', 'no commands dir');
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
catch (e) {
|
|
192
|
-
report(onStep, cmdStepId, 'Sync Claude commands', 'error', String(e));
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
catch (e) {
|
|
197
|
-
report(onStep, stepId, `Sync config for ${cli}`, 'error', String(e));
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
//# sourceMappingURL=sync.js.map
|
package/dist/ui/SyncUI.d.ts
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import type { AI_CLI } from '../types/index.js';
|
|
3
|
-
interface Props {
|
|
4
|
-
target: AI_CLI | 'all';
|
|
5
|
-
mode: 'overwrite' | 'merge';
|
|
6
|
-
dryRun: boolean;
|
|
7
|
-
}
|
|
8
|
-
export default function SyncUI({ target, mode, dryRun }: Props): React.JSX.Element;
|
|
9
|
-
export {};
|
|
10
|
-
//# sourceMappingURL=SyncUI.d.ts.map
|
package/dist/ui/SyncUI.js
DELETED
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import React, { useEffect, useState } from 'react';
|
|
2
|
-
import { Box, Text, useApp, useInput } from 'ink';
|
|
3
|
-
import Spinner from 'ink-spinner';
|
|
4
|
-
import { syncAIConfig } from '../commands/sync.js';
|
|
5
|
-
import Header from './Header.js';
|
|
6
|
-
import { theme } from './theme.js';
|
|
7
|
-
export default function SyncUI({ target, mode, dryRun }) {
|
|
8
|
-
const { exit } = useApp();
|
|
9
|
-
const [steps, setSteps] = useState([]);
|
|
10
|
-
const [finished, setFinished] = useState(false);
|
|
11
|
-
useEffect(() => {
|
|
12
|
-
syncAIConfig({ target, mode, projectDir: process.cwd(), dryRun }, (step) => setSteps(prev => {
|
|
13
|
-
const idx = prev.findIndex(s => s.id === step.id);
|
|
14
|
-
if (idx >= 0) {
|
|
15
|
-
const next = [...prev];
|
|
16
|
-
next[idx] = step;
|
|
17
|
-
return next;
|
|
18
|
-
}
|
|
19
|
-
return [...prev, step];
|
|
20
|
-
})).then(() => setFinished(true));
|
|
21
|
-
}, [target, mode, dryRun]);
|
|
22
|
-
useInput((_, key) => {
|
|
23
|
-
if (finished && (key.return || key.escape))
|
|
24
|
-
exit();
|
|
25
|
-
});
|
|
26
|
-
const done = steps.filter(s => s.status === 'done').length;
|
|
27
|
-
const errors = steps.filter(s => s.status === 'error').length;
|
|
28
|
-
return (React.createElement(Box, { flexDirection: "column", padding: 1 },
|
|
29
|
-
React.createElement(Header, { subtitle: "sync", dryRun: dryRun }),
|
|
30
|
-
React.createElement(Box, { flexDirection: "column" }, steps.map(step => (React.createElement(Box, { key: step.id, marginLeft: 2 }, step.status === 'running' ? (React.createElement(Text, { color: theme.warning },
|
|
31
|
-
React.createElement(Spinner, { type: "dots" }),
|
|
32
|
-
' ',
|
|
33
|
-
step.label)) : step.status === 'done' ? (React.createElement(Text, { color: theme.success },
|
|
34
|
-
'\u2713',
|
|
35
|
-
" ",
|
|
36
|
-
step.label,
|
|
37
|
-
step.detail && React.createElement(Text, { color: theme.muted, dimColor: true },
|
|
38
|
-
" ",
|
|
39
|
-
step.detail))) : step.status === 'error' ? (React.createElement(Text, { color: theme.error },
|
|
40
|
-
'\u2717',
|
|
41
|
-
" ",
|
|
42
|
-
step.label,
|
|
43
|
-
step.detail && React.createElement(Text, { color: theme.muted, dimColor: true },
|
|
44
|
-
" ",
|
|
45
|
-
step.detail))) : (React.createElement(Text, { color: theme.muted },
|
|
46
|
-
'\u2013',
|
|
47
|
-
" ",
|
|
48
|
-
step.label,
|
|
49
|
-
step.detail && React.createElement(Text, { dimColor: true },
|
|
50
|
-
" ",
|
|
51
|
-
step.detail))))))),
|
|
52
|
-
finished && (React.createElement(Box, { marginTop: 1, flexDirection: "column" },
|
|
53
|
-
React.createElement(Text, { bold: true, color: errors > 0 ? theme.warning : theme.success },
|
|
54
|
-
dryRun ? '\u25cb Dry run complete' : '\u2713 Sync complete',
|
|
55
|
-
' ',
|
|
56
|
-
"(",
|
|
57
|
-
done,
|
|
58
|
-
" done",
|
|
59
|
-
errors > 0 ? `, ${errors} errors` : '',
|
|
60
|
-
")"),
|
|
61
|
-
React.createElement(Box, { marginTop: 1 },
|
|
62
|
-
React.createElement(Text, { color: theme.muted, dimColor: true }, "Press Enter to exit"))))));
|
|
63
|
-
}
|
|
64
|
-
//# sourceMappingURL=SyncUI.js.map
|
package/tasks/_TEMPLATE/plan.md
DELETED