@aiwg/cli 2026.8.7 → 2026.8.8
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/src/artifacts/move.js +40 -2
- package/dist/src/cli/handlers/regenerate.js +19 -9
- package/dist/src/cli/handlers/sessions.js +5 -4
- package/dist/src/cli/handlers/subcommands.js +30 -0
- package/dist/src/cli/handlers/use.js +16 -19
- package/dist/src/cli/regenerate-selector.js +94 -0
- package/dist/src/extensions/project-local-doctor.js +10 -6
- package/dist/src/extensions/project-local-gitignore.js +8 -4
- package/dist/src/extensions/project-quickref.js +197 -10
- package/package.json +1 -1
- package/tools/plugin/package-plugins.mjs +53 -0
|
@@ -6,10 +6,24 @@ import { syncFortemiCoreIndex } from './fortemi-core-sync.js';
|
|
|
6
6
|
import { PROJECT_AIWG_LOCATION_FILE, expandProjectArtifactPath, resolveProjectAiwgDir, } from '../config/project-artifacts.js';
|
|
7
7
|
const GITIGNORE_BLOCK = [
|
|
8
8
|
'',
|
|
9
|
-
'# AIWG artifact
|
|
9
|
+
'# AIWG external artifact corpus (retain the project-local control plane)',
|
|
10
|
+
'!.aiwg/',
|
|
11
|
+
'.aiwg/*',
|
|
12
|
+
'!.aiwg/AIWG.md',
|
|
13
|
+
'!.aiwg/aiwg.config',
|
|
14
|
+
'!.aiwg/frameworks/',
|
|
15
|
+
'.aiwg/frameworks/*',
|
|
16
|
+
'!.aiwg/frameworks/registry.json',
|
|
17
|
+
'',
|
|
18
|
+
'# AIWG artifact root pointer (machine-local/private path)',
|
|
10
19
|
PROJECT_AIWG_LOCATION_FILE,
|
|
11
20
|
'',
|
|
12
21
|
].join('\n');
|
|
22
|
+
const LOCAL_CONTROL_PLANE_FILES = [
|
|
23
|
+
'AIWG.md',
|
|
24
|
+
'aiwg.config',
|
|
25
|
+
path.join('frameworks', 'registry.json'),
|
|
26
|
+
];
|
|
13
27
|
async function exists(filePath) {
|
|
14
28
|
try {
|
|
15
29
|
await access(filePath);
|
|
@@ -49,7 +63,8 @@ async function ensureGitignorePointer(projectDir, dryRun) {
|
|
|
49
63
|
throw error;
|
|
50
64
|
}
|
|
51
65
|
const lines = current.split(/\r?\n/).map(line => line.trim());
|
|
52
|
-
|
|
66
|
+
const required = GITIGNORE_BLOCK.split(/\r?\n/).map(line => line.trim()).filter(Boolean);
|
|
67
|
+
if (required.every(line => lines.includes(line)))
|
|
53
68
|
return false;
|
|
54
69
|
if (!dryRun) {
|
|
55
70
|
const separator = current.length === 0 || current.endsWith('\n') ? '' : '\n';
|
|
@@ -57,6 +72,28 @@ async function ensureGitignorePointer(projectDir, dryRun) {
|
|
|
57
72
|
}
|
|
58
73
|
return true;
|
|
59
74
|
}
|
|
75
|
+
async function materializeLocalControlPlane(projectDir, artifactRoot, dryRun) {
|
|
76
|
+
const localRoot = path.join(projectDir, '.aiwg');
|
|
77
|
+
for (const relativePath of LOCAL_CONTROL_PLANE_FILES) {
|
|
78
|
+
const sourcePath = path.join(artifactRoot, relativePath);
|
|
79
|
+
if (!(await exists(sourcePath)))
|
|
80
|
+
continue;
|
|
81
|
+
const destinationPath = path.join(localRoot, relativePath);
|
|
82
|
+
const sourceContent = await readFile(sourcePath);
|
|
83
|
+
if (await exists(destinationPath)) {
|
|
84
|
+
const destinationContent = await readFile(destinationPath);
|
|
85
|
+
if (!sourceContent.equals(destinationContent)) {
|
|
86
|
+
throw new Error(`Local AIWG control-plane file differs from the external artifact root: ${destinationPath}. `
|
|
87
|
+
+ 'Reconcile the files before attaching the corpus.');
|
|
88
|
+
}
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
if (!dryRun) {
|
|
92
|
+
await mkdir(path.dirname(destinationPath), { recursive: true });
|
|
93
|
+
await writeFile(destinationPath, sourceContent);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
60
97
|
async function writePointer(projectDir, pointerValue, dryRun) {
|
|
61
98
|
const pointerPath = path.join(projectDir, PROJECT_AIWG_LOCATION_FILE);
|
|
62
99
|
if (!dryRun) {
|
|
@@ -134,6 +171,7 @@ export async function moveProjectArtifacts(options) {
|
|
|
134
171
|
if (!dryRun && !attach) {
|
|
135
172
|
await moveDirectory(source, destination);
|
|
136
173
|
}
|
|
174
|
+
await materializeLocalControlPlane(projectDir, destination, dryRun);
|
|
137
175
|
await writePointer(projectDir, pointerValue, dryRun);
|
|
138
176
|
let reindexed = false;
|
|
139
177
|
let fortemiSynced = false;
|
|
@@ -26,6 +26,7 @@ import { generate as generateContextFiles, discoverDeployedArtifacts, shouldEmit
|
|
|
26
26
|
import { resolveActiveProvider } from '../provider-resolution.js';
|
|
27
27
|
import { getProviderContextDiscoveryPathStrings } from '../../providers/provider-definitions.js';
|
|
28
28
|
import { projectAiwgPath } from '../../config/project-artifacts.js';
|
|
29
|
+
import { selectRegenerateBranch } from '../regenerate-selector.js';
|
|
29
30
|
async function handleRegenerate(args, cwd) {
|
|
30
31
|
if (args.includes('--help') || args.includes('-h')) {
|
|
31
32
|
console.log(`
|
|
@@ -40,7 +41,8 @@ async function handleRegenerate(args, cwd) {
|
|
|
40
41
|
or commands — use 'aiwg refresh' for that.
|
|
41
42
|
|
|
42
43
|
Options:
|
|
43
|
-
|
|
44
|
+
(no branch flag) Intelligently select workspace refresh or adoption preview
|
|
45
|
+
--workspace Explicit canonical WORKSPACE.md → AIWG.md graph
|
|
44
46
|
--existing-project Transactionally extract an established project into WORKSPACE.md
|
|
45
47
|
--legacy, --full-inject Legacy inline compatibility branch
|
|
46
48
|
--apply Apply --existing-project after its mandatory preflight
|
|
@@ -57,6 +59,7 @@ async function handleRegenerate(args, cwd) {
|
|
|
57
59
|
aiwg regenerate --workspace
|
|
58
60
|
aiwg regenerate --existing-project --dry-run
|
|
59
61
|
aiwg regenerate --existing-project --apply
|
|
62
|
+
aiwg regenerate --apply
|
|
60
63
|
aiwg regenerate --full-inject
|
|
61
64
|
aiwg regenerate --dry-run
|
|
62
65
|
aiwg regenerate --provider codex
|
|
@@ -69,9 +72,9 @@ async function handleRegenerate(args, cwd) {
|
|
|
69
72
|
const skipAiwgMd = args.includes('--no-aiwg-md');
|
|
70
73
|
const skipAgentsMd = args.includes('--no-agents-md');
|
|
71
74
|
const skipWorkspaceMd = args.includes('--no-workspace-md');
|
|
72
|
-
const
|
|
73
|
-
const
|
|
74
|
-
const
|
|
75
|
+
const requestedLegacy = args.includes('--legacy') || args.includes('--full-inject');
|
|
76
|
+
const requestedWorkspace = args.includes('--workspace');
|
|
77
|
+
const requestedExistingProject = args.includes('--existing-project');
|
|
75
78
|
const apply = args.includes('--apply');
|
|
76
79
|
const valueFlags = new Set(['--provider']);
|
|
77
80
|
const booleanFlags = new Set([
|
|
@@ -95,23 +98,27 @@ async function handleRegenerate(args, cwd) {
|
|
|
95
98
|
});
|
|
96
99
|
}
|
|
97
100
|
}
|
|
98
|
-
const selectedBranches = Number(
|
|
101
|
+
const selectedBranches = Number(requestedLegacy) + Number(requestedWorkspace) + Number(requestedExistingProject);
|
|
99
102
|
if (selectedBranches > 1)
|
|
100
103
|
throw new AiwgError({
|
|
101
104
|
code: 'ERR_USAGE_CONFLICTING_FLAGS',
|
|
102
105
|
message: 'Choose exactly one regenerate branch: --workspace, --existing-project, or --full-inject.',
|
|
103
106
|
exitCode: EXIT_CODES.USAGE,
|
|
104
107
|
});
|
|
105
|
-
if (
|
|
108
|
+
if (dryRun && apply)
|
|
106
109
|
throw new AiwgError({
|
|
107
110
|
code: 'ERR_USAGE_CONFLICTING_FLAGS',
|
|
108
|
-
message: '
|
|
111
|
+
message: 'Choose either --dry-run or --apply.',
|
|
109
112
|
exitCode: EXIT_CODES.USAGE,
|
|
110
113
|
});
|
|
111
|
-
|
|
114
|
+
const selection = await selectRegenerateBranch(cwd, args);
|
|
115
|
+
const legacy = selection.branch === 'legacy';
|
|
116
|
+
const existingProject = selection.branch === 'existing-project';
|
|
117
|
+
if (apply && !existingProject)
|
|
112
118
|
throw new AiwgError({
|
|
113
119
|
code: 'ERR_USAGE_CONFLICTING_FLAGS',
|
|
114
|
-
message: '
|
|
120
|
+
message: '--apply is only valid when the existing-project branch is selected.',
|
|
121
|
+
hint: 'Use `aiwg regenerate --existing-project --apply`, or run without --apply to inspect the selected branch.',
|
|
115
122
|
exitCode: EXIT_CODES.USAGE,
|
|
116
123
|
});
|
|
117
124
|
if (existingProject && (force || skipAiwgMd || skipAgentsMd || skipWorkspaceMd))
|
|
@@ -140,6 +147,9 @@ async function handleRegenerate(args, cwd) {
|
|
|
140
147
|
console.log(` Provider: ${provider}`);
|
|
141
148
|
console.log(` Target: ${target}`);
|
|
142
149
|
console.log(` Branch: ${legacy ? 'legacy full injection' : existingProject ? 'canonical existing-project extraction' : 'canonical workspace graph'}`);
|
|
150
|
+
console.log(` Selected: ${selection.explicit ? 'explicit' : 'inferred'} — ${selection.reason}`);
|
|
151
|
+
if (selection.evidence.length > 0)
|
|
152
|
+
console.log(` Evidence: ${selection.evidence.join(', ')}`);
|
|
143
153
|
if (existingProject) {
|
|
144
154
|
const preflight = await migrateWorkspaceContext(target, {
|
|
145
155
|
dryRun: true,
|
|
@@ -1203,15 +1203,16 @@ function projectRootCandidate(start) {
|
|
|
1203
1203
|
catch {
|
|
1204
1204
|
current = resolve(start);
|
|
1205
1205
|
}
|
|
1206
|
-
let gitRoot = null;
|
|
1207
1206
|
while (true) {
|
|
1208
1207
|
if (existsSync(resolve(current, '.aiwg', 'aiwg.config')))
|
|
1209
1208
|
return current;
|
|
1210
|
-
|
|
1211
|
-
|
|
1209
|
+
// A repository root is the project boundary. Do not let an unrelated
|
|
1210
|
+
// ancestor workspace configuration capture session catalog reads.
|
|
1211
|
+
if (existsSync(resolve(current, '.git')))
|
|
1212
|
+
return current;
|
|
1212
1213
|
const parent = dirname(current);
|
|
1213
1214
|
if (parent === current)
|
|
1214
|
-
return
|
|
1215
|
+
return null;
|
|
1215
1216
|
current = parent;
|
|
1216
1217
|
}
|
|
1217
1218
|
}
|
|
@@ -47,6 +47,8 @@ export const quickrefHandler = {
|
|
|
47
47
|
console.log(`${verb} ${result.skillName}`);
|
|
48
48
|
console.log(` Source: ${result.sourcePath}`);
|
|
49
49
|
console.log(` Output: ${result.outputPath}`);
|
|
50
|
+
for (const warning of result.warnings)
|
|
51
|
+
console.log(` Warning: ${warning}`);
|
|
50
52
|
if (ctx.dryRun) {
|
|
51
53
|
console.log('\n--- preview ---\n');
|
|
52
54
|
console.log(result.content);
|
|
@@ -850,6 +852,26 @@ export const promoteHandler = {
|
|
|
850
852
|
console.log(`✓ Promoted '${positional}' → ${result.plan?.destination}`);
|
|
851
853
|
if (cleanup) {
|
|
852
854
|
console.log(' Source removed from .aiwg/');
|
|
855
|
+
try {
|
|
856
|
+
const { deployProjectQuickref, generateProjectQuickref, hasProjectQuickref } = await import('../../extensions/project-quickref.js');
|
|
857
|
+
if (await hasProjectQuickref(projectDir)) {
|
|
858
|
+
if (config.providers.length > 0) {
|
|
859
|
+
for (const provider of config.providers)
|
|
860
|
+
await deployProjectQuickref(projectDir, provider);
|
|
861
|
+
console.log(` Managed project quickref refreshed for: ${config.providers.join(', ')}.`);
|
|
862
|
+
}
|
|
863
|
+
else {
|
|
864
|
+
await generateProjectQuickref(projectDir);
|
|
865
|
+
console.log(' Managed project quickref generated; no providers are configured for deployment.');
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
else {
|
|
869
|
+
console.log(' Managed project quickref is now empty; run `aiwg doctor --project-local` to inspect deployed stale copies.');
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
catch (error) {
|
|
873
|
+
console.log(` Managed project quickref refresh failed: ${error.message}`);
|
|
874
|
+
}
|
|
853
875
|
}
|
|
854
876
|
return { exitCode: 0 };
|
|
855
877
|
}
|
|
@@ -956,6 +978,14 @@ export const newBundleHandler = {
|
|
|
956
978
|
catch {
|
|
957
979
|
// .gitignore management is best-effort; don't fail the scaffold
|
|
958
980
|
}
|
|
981
|
+
try {
|
|
982
|
+
const { generateProjectQuickref } = await import('../../extensions/project-quickref.js');
|
|
983
|
+
await generateProjectQuickref(ctx.cwd);
|
|
984
|
+
console.log(' → Managed project quickref refreshed from discovered capabilities.');
|
|
985
|
+
}
|
|
986
|
+
catch (error) {
|
|
987
|
+
console.log(` → Managed project quickref refresh deferred: ${error.message}`);
|
|
988
|
+
}
|
|
959
989
|
// #1235 / #1758 — auto-rebuild the project graph and refresh the
|
|
960
990
|
// Fortemi Core static cache so the new bundle is immediately
|
|
961
991
|
// discoverable via top-level `aiwg discover` / `aiwg show`.
|
|
@@ -1079,18 +1079,17 @@ async function deployProjectLocalBundles(opts) {
|
|
|
1079
1079
|
: discovery.bundles.filter(b => b.type !== 'provider');
|
|
1080
1080
|
if (targetBundles.length === 0) {
|
|
1081
1081
|
if (!onlyBundleId) {
|
|
1082
|
-
const {
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
try {
|
|
1082
|
+
const { hasProjectQuickref, deployProjectQuickref } = await import('../../extensions/project-quickref.js');
|
|
1083
|
+
try {
|
|
1084
|
+
if (await hasProjectQuickref(projectDir)) {
|
|
1086
1085
|
await deployProjectQuickref(projectDir, provider, { dryRun });
|
|
1087
1086
|
if (verbose || dryRun)
|
|
1088
1087
|
ui.dim(` + project quickref -> ${provider}`);
|
|
1089
1088
|
}
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
}
|
|
1089
|
+
}
|
|
1090
|
+
catch (error) {
|
|
1091
|
+
ui.warn(`Project quickref deployment failed: ${error.message}`);
|
|
1092
|
+
return { deployed: 0, failed: 1, bundles: [] };
|
|
1094
1093
|
}
|
|
1095
1094
|
}
|
|
1096
1095
|
return { deployed: 0, failed: 0, bundles: [] };
|
|
@@ -1204,22 +1203,20 @@ async function deployProjectLocalBundles(opts) {
|
|
|
1204
1203
|
}
|
|
1205
1204
|
}
|
|
1206
1205
|
}
|
|
1207
|
-
//
|
|
1208
|
-
//
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
if (quickref.exists) {
|
|
1213
|
-
try {
|
|
1206
|
+
// Refresh the project kernel quickref from either legacy operator input or
|
|
1207
|
+
// managed project-local discovery whenever bundles deploy.
|
|
1208
|
+
const { hasProjectQuickref, deployProjectQuickref } = await import('../../extensions/project-quickref.js');
|
|
1209
|
+
try {
|
|
1210
|
+
if (await hasProjectQuickref(projectDir)) {
|
|
1214
1211
|
const quickrefResult = await deployProjectQuickref(projectDir, provider, { dryRun });
|
|
1215
1212
|
if (verbose || dryRun) {
|
|
1216
1213
|
ui.dim(` + project quickref -> ${quickrefResult.provider}${quickrefResult.emulated ? ' (emulated)' : ''}`);
|
|
1217
1214
|
}
|
|
1218
1215
|
}
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
}
|
|
1216
|
+
}
|
|
1217
|
+
catch (error) {
|
|
1218
|
+
failed++;
|
|
1219
|
+
ui.warn(`Project quickref deployment failed: ${error.message}`);
|
|
1223
1220
|
}
|
|
1224
1221
|
return { deployed, failed, bundles: targetBundles };
|
|
1225
1222
|
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { promises as fs } from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import { auditWorkspaceContext, PROJECT_EXTRACTION_END, PROJECT_EXTRACTION_START, WORKSPACE_MANAGED_END, WORKSPACE_MANAGED_START, WORKSPACE_OPERATOR_END, WORKSPACE_OPERATOR_START, } from '../smiths/context-pipeline/index.js';
|
|
4
|
+
import { AiwgError, EXIT_CODES } from './errors.js';
|
|
5
|
+
function markerPair(content, start, end) {
|
|
6
|
+
const startIndex = content.indexOf(start);
|
|
7
|
+
const endIndex = content.indexOf(end);
|
|
8
|
+
const present = startIndex >= 0 || endIndex >= 0;
|
|
9
|
+
return {
|
|
10
|
+
present,
|
|
11
|
+
valid: !present || (startIndex >= 0 && endIndex > startIndex
|
|
12
|
+
&& content.indexOf(start, startIndex + start.length) < 0
|
|
13
|
+
&& content.indexOf(end, endIndex + end.length) < 0),
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
function malformedWorkspace(message) {
|
|
17
|
+
throw new AiwgError({
|
|
18
|
+
code: 'ERR_USAGE_REGENERATE_STATE_MALFORMED',
|
|
19
|
+
message,
|
|
20
|
+
hint: 'Repair the managed marker pair or restore WORKSPACE.md from version control, then rerun `aiwg regenerate --dry-run`.',
|
|
21
|
+
exitCode: EXIT_CODES.USAGE,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
async function readOptional(filePath) {
|
|
25
|
+
try {
|
|
26
|
+
return await fs.readFile(filePath, 'utf8');
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
if (error.code === 'ENOENT')
|
|
30
|
+
return null;
|
|
31
|
+
throw error;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
export async function selectRegenerateBranch(cwd, args) {
|
|
35
|
+
const legacy = args.includes('--legacy') || args.includes('--full-inject');
|
|
36
|
+
const workspace = args.includes('--workspace');
|
|
37
|
+
const existingProject = args.includes('--existing-project');
|
|
38
|
+
if (legacy)
|
|
39
|
+
return { branch: 'legacy', state: 'legacy-context', reason: 'explicit legacy compatibility branch', evidence: ['--legacy/--full-inject'], explicit: true };
|
|
40
|
+
if (workspace)
|
|
41
|
+
return { branch: 'workspace', state: 'fresh', reason: 'explicit canonical workspace branch', evidence: ['--workspace'], explicit: true };
|
|
42
|
+
if (existingProject)
|
|
43
|
+
return { branch: 'existing-project', state: 'established-unextracted', reason: 'explicit existing-project adoption branch', evidence: ['--existing-project'], explicit: true };
|
|
44
|
+
const workspaceContent = await readOptional(path.join(cwd, 'WORKSPACE.md'));
|
|
45
|
+
let workspaceMarkers = null;
|
|
46
|
+
if (workspaceContent !== null) {
|
|
47
|
+
workspaceMarkers = {
|
|
48
|
+
managed: markerPair(workspaceContent, WORKSPACE_MANAGED_START, WORKSPACE_MANAGED_END),
|
|
49
|
+
operator: markerPair(workspaceContent, WORKSPACE_OPERATOR_START, WORKSPACE_OPERATOR_END),
|
|
50
|
+
extraction: markerPair(workspaceContent, PROJECT_EXTRACTION_START, PROJECT_EXTRACTION_END),
|
|
51
|
+
};
|
|
52
|
+
const { managed, operator, extraction } = workspaceMarkers;
|
|
53
|
+
if (!managed.valid || !operator.valid || !extraction.valid) {
|
|
54
|
+
malformedWorkspace('WORKSPACE.md contains an incomplete, duplicated, or out-of-order AIWG managed marker pair.');
|
|
55
|
+
}
|
|
56
|
+
if (extraction.present && !managed.present)
|
|
57
|
+
malformedWorkspace('WORKSPACE.md contains a project-extraction block without the canonical workspace managed graph.');
|
|
58
|
+
if (managed.present !== operator.present)
|
|
59
|
+
malformedWorkspace('WORKSPACE.md contains only part of the canonical managed/operator structure.');
|
|
60
|
+
}
|
|
61
|
+
const audit = await auditWorkspaceContext(cwd);
|
|
62
|
+
const projectSources = audit.plan.projectSources;
|
|
63
|
+
const operatorSources = audit.sources
|
|
64
|
+
.filter((source) => source.path !== 'WORKSPACE.md' && source.operatorContent.trim().length > 0)
|
|
65
|
+
.map((source) => source.path);
|
|
66
|
+
if (workspaceContent !== null) {
|
|
67
|
+
const { managed, extraction } = workspaceMarkers;
|
|
68
|
+
if (managed.present && extraction.present)
|
|
69
|
+
return {
|
|
70
|
+
branch: 'workspace', state: 'adopted', reason: 'canonical workspace already contains an extracted project snapshot', evidence: ['WORKSPACE.md project-extraction marker'], explicit: false,
|
|
71
|
+
};
|
|
72
|
+
if (managed.present && projectSources.length > 0)
|
|
73
|
+
return {
|
|
74
|
+
branch: 'existing-project', state: 'canonical-unextracted', reason: 'canonical workspace exists but stable project metadata has not been adopted', evidence: projectSources, explicit: false,
|
|
75
|
+
};
|
|
76
|
+
if (managed.present)
|
|
77
|
+
return {
|
|
78
|
+
branch: 'workspace', state: 'fresh', reason: 'canonical workspace exists and no stable project sources were detected', evidence: ['WORKSPACE.md managed graph'], explicit: false,
|
|
79
|
+
};
|
|
80
|
+
return {
|
|
81
|
+
branch: 'existing-project', state: 'operator-owned-workspace', reason: 'operator-owned WORKSPACE.md requires transactional adoption before canonical refresh', evidence: ['WORKSPACE.md without AIWG managed markers', ...projectSources], explicit: false,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
if (projectSources.length > 0)
|
|
85
|
+
return {
|
|
86
|
+
branch: 'existing-project', state: 'established-unextracted', reason: 'stable existing-project sources were detected without an extracted workspace snapshot', evidence: projectSources, explicit: false,
|
|
87
|
+
};
|
|
88
|
+
if (audit.legacyCompatible || operatorSources.length > 0)
|
|
89
|
+
return {
|
|
90
|
+
branch: 'existing-project', state: 'legacy-context', reason: 'operator-authored provider context requires transactional adoption', evidence: operatorSources, explicit: false,
|
|
91
|
+
};
|
|
92
|
+
return { branch: 'workspace', state: 'fresh', reason: 'no prior workspace setup or stable project sources were detected', evidence: [], explicit: false };
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=regenerate-selector.js.map
|
|
@@ -54,12 +54,15 @@ export async function buildProjectLocalDoctorSection(opts) {
|
|
|
54
54
|
const quickrefAudit = await auditProjectQuickref(projectDir, config?.providers ?? []);
|
|
55
55
|
const quickrefErrors = [...quickrefAudit.errors];
|
|
56
56
|
if (quickrefAudit.exists) {
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
57
|
+
for (const name of ['quickref.json', 'quickref.config.json']) {
|
|
58
|
+
const sourcePath = projectAiwgPath(projectDir, name);
|
|
59
|
+
const quickrefRelPath = projectRelativePathIfInside(projectDir, sourcePath);
|
|
60
|
+
const ignored = quickrefRelPath
|
|
61
|
+
? await checkBundleManifestIgnored(projectDir, quickrefRelPath)
|
|
62
|
+
: null;
|
|
63
|
+
if (ignored === true && quickrefRelPath) {
|
|
64
|
+
quickrefErrors.push(`${quickrefRelPath} is ignored by git; operator project quickref input must be committed`);
|
|
65
|
+
}
|
|
63
66
|
}
|
|
64
67
|
}
|
|
65
68
|
// No project-local content → no section at all
|
|
@@ -236,6 +239,7 @@ export async function buildProjectLocalDoctorSection(opts) {
|
|
|
236
239
|
}
|
|
237
240
|
lines.push(' Project-local bundle source should be tracked. Add to .gitignore:');
|
|
238
241
|
lines.push(' !.aiwg/quickref.json');
|
|
242
|
+
lines.push(' !.aiwg/quickref.config.json');
|
|
239
243
|
lines.push(' !.aiwg/addons/');
|
|
240
244
|
lines.push(' !.aiwg/extensions/');
|
|
241
245
|
lines.push(' !.aiwg/frameworks/');
|
|
@@ -39,6 +39,7 @@ export const AIWG_GITIGNORE_BLOCK = [
|
|
|
39
39
|
AIWG_GITIGNORE_SENTINEL,
|
|
40
40
|
'!.aiwg/aiwg.config',
|
|
41
41
|
'!.aiwg/quickref.json',
|
|
42
|
+
'!.aiwg/quickref.config.json',
|
|
42
43
|
'!.aiwg/addons/',
|
|
43
44
|
'!.aiwg/extensions/',
|
|
44
45
|
'!.aiwg/frameworks/',
|
|
@@ -120,11 +121,14 @@ export async function appendAiwgSourceTrackBlock(projectDir) {
|
|
|
120
121
|
// runs.
|
|
121
122
|
if (report.hasManagedBlock) {
|
|
122
123
|
const path = join(projectDir, '.gitignore');
|
|
123
|
-
|
|
124
|
-
|
|
124
|
+
let existing = await readFile(path, 'utf8');
|
|
125
|
+
const required = ['!.aiwg/quickref.json', '!.aiwg/quickref.config.json'];
|
|
126
|
+
const missing = required.filter(negation => !existing.split(/\r?\n/).some(line => line.trim() === negation));
|
|
127
|
+
if (missing.length > 0) {
|
|
125
128
|
const sep = existing.endsWith('\n') ? '' : '\n';
|
|
126
|
-
|
|
127
|
-
|
|
129
|
+
existing = `${existing}${sep}${missing.join('\n')}\n`;
|
|
130
|
+
await writeFile(path, existing, 'utf8');
|
|
131
|
+
return { added: true, reason: `updated managed block to track ${missing.join(', ')}` };
|
|
128
132
|
}
|
|
129
133
|
return { added: false, reason: 'block already present — no change' };
|
|
130
134
|
}
|
|
@@ -7,13 +7,15 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { createHash } from 'crypto';
|
|
9
9
|
import { access, mkdir, readFile, readdir, rm, writeFile } from 'fs/promises';
|
|
10
|
-
import { dirname, isAbsolute, join, resolve } from 'path';
|
|
10
|
+
import { basename, dirname, isAbsolute, join, resolve } from 'path';
|
|
11
11
|
import { homedir } from 'os';
|
|
12
12
|
import { z } from 'zod';
|
|
13
13
|
import { getProviderDefinition, normalizeProviderDefinitionId, } from '../providers/provider-definitions.js';
|
|
14
14
|
import { OPERATIONAL_SHOW_TYPES } from '../artifacts/types.js';
|
|
15
15
|
import { projectAiwgPath } from '../config/project-artifacts.js';
|
|
16
16
|
import { appendAiwgSourceTrackBlock } from './project-local-gitignore.js';
|
|
17
|
+
import { discoverProjectLocalBundles } from './project-local-discovery.js';
|
|
18
|
+
import { enumerateBundleArtifacts } from './shadow-resolver.js';
|
|
17
19
|
const OWNERSHIP_MARKER = '.aiwg-project-quickref.json';
|
|
18
20
|
const ShowHintSchema = z.object({
|
|
19
21
|
type: z.enum(OPERATIONAL_SHOW_TYPES),
|
|
@@ -35,6 +37,25 @@ export const ProjectQuickrefSchema = z.object({
|
|
|
35
37
|
precedence: z.string().min(1).max(1024),
|
|
36
38
|
entries: z.array(QuickrefEntrySchema).min(1).max(50),
|
|
37
39
|
}).strict();
|
|
40
|
+
const QuickrefOverrideSchema = z.object({
|
|
41
|
+
title: z.string().min(1).max(128).optional(),
|
|
42
|
+
summary: z.string().min(1).max(512).optional(),
|
|
43
|
+
discover: z.array(z.string().min(1).max(256)).max(10).optional(),
|
|
44
|
+
show: z.array(ShowHintSchema).max(20).optional(),
|
|
45
|
+
hidden: z.boolean().optional(),
|
|
46
|
+
order: z.number().int().optional(),
|
|
47
|
+
}).strict();
|
|
48
|
+
export const ProjectQuickrefConfigSchema = z.object({
|
|
49
|
+
version: z.literal('1'),
|
|
50
|
+
project: ProjectQuickrefSchema.shape.project.optional(),
|
|
51
|
+
precedence: z.string().min(1).max(1024).optional(),
|
|
52
|
+
entries: z.array(QuickrefEntrySchema).max(50).default([]),
|
|
53
|
+
discovery: z.object({
|
|
54
|
+
enabled: z.boolean().default(true),
|
|
55
|
+
excludeBundles: z.array(z.string().min(1)).max(200).default([]),
|
|
56
|
+
overrides: z.record(z.string(), QuickrefOverrideSchema).default({}),
|
|
57
|
+
}).strict().default({ enabled: true, excludeBundles: [], overrides: {} }),
|
|
58
|
+
}).strict();
|
|
38
59
|
function sha256(content) {
|
|
39
60
|
return createHash('sha256').update(content).digest('hex');
|
|
40
61
|
}
|
|
@@ -78,6 +99,158 @@ export async function loadProjectQuickref(projectDir) {
|
|
|
78
99
|
return { sourcePath, errors: [`${sourcePath}: invalid JSON: ${error.message}`], exists: true };
|
|
79
100
|
}
|
|
80
101
|
}
|
|
102
|
+
function slug(value) {
|
|
103
|
+
const normalized = value.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
|
|
104
|
+
return normalized || 'project';
|
|
105
|
+
}
|
|
106
|
+
async function inferredProject(projectDir) {
|
|
107
|
+
let packageName = basename(resolve(projectDir));
|
|
108
|
+
let description = `Managed project-specific orientation for ${packageName}.`;
|
|
109
|
+
try {
|
|
110
|
+
const parsed = JSON.parse(await readFile(join(projectDir, 'package.json'), 'utf8'));
|
|
111
|
+
if (parsed.name)
|
|
112
|
+
packageName = parsed.name;
|
|
113
|
+
if (parsed.description)
|
|
114
|
+
description = parsed.description.slice(0, 512);
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
// package metadata is optional
|
|
118
|
+
}
|
|
119
|
+
const id = slug(packageName);
|
|
120
|
+
const name = packageName
|
|
121
|
+
.replace(/^@[^/]+\//, '')
|
|
122
|
+
.split(/[-_]/)
|
|
123
|
+
.filter(Boolean)
|
|
124
|
+
.map(part => part[0]?.toUpperCase() + part.slice(1))
|
|
125
|
+
.join(' ') || 'Project';
|
|
126
|
+
return { id, name, description };
|
|
127
|
+
}
|
|
128
|
+
async function loadManagedConfig(projectDir) {
|
|
129
|
+
const path = projectAiwgPath(projectDir, 'quickref.config.json');
|
|
130
|
+
const raw = await readIfPresent(path);
|
|
131
|
+
if (raw === null)
|
|
132
|
+
return { path };
|
|
133
|
+
let json;
|
|
134
|
+
try {
|
|
135
|
+
json = JSON.parse(raw);
|
|
136
|
+
}
|
|
137
|
+
catch (error) {
|
|
138
|
+
throw new Error(`${path}: invalid JSON: ${error.message}`);
|
|
139
|
+
}
|
|
140
|
+
const parsed = ProjectQuickrefConfigSchema.safeParse(json);
|
|
141
|
+
if (!parsed.success) {
|
|
142
|
+
throw new Error(parsed.error.issues.map(issue => `${path}: ${issue.path.join('.') || '(root)'}: ${issue.message}`).join('\n'));
|
|
143
|
+
}
|
|
144
|
+
return { path, config: parsed.data };
|
|
145
|
+
}
|
|
146
|
+
function dedupe(values) {
|
|
147
|
+
const seen = new Set();
|
|
148
|
+
return values.filter(value => {
|
|
149
|
+
const key = value.trim().toLowerCase();
|
|
150
|
+
if (!key || seen.has(key))
|
|
151
|
+
return false;
|
|
152
|
+
seen.add(key);
|
|
153
|
+
return true;
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
function dedupeShow(values) {
|
|
157
|
+
const seen = new Set();
|
|
158
|
+
return values.filter(value => {
|
|
159
|
+
const key = `${value.type}:${value.name.toLowerCase()}`;
|
|
160
|
+
if (seen.has(key))
|
|
161
|
+
return false;
|
|
162
|
+
seen.add(key);
|
|
163
|
+
return true;
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
/** Resolve legacy operator input or synthesize a managed definition from project-local bundles. */
|
|
167
|
+
export async function resolveProjectQuickref(projectDir) {
|
|
168
|
+
const legacy = await loadProjectQuickref(projectDir);
|
|
169
|
+
if (legacy.exists) {
|
|
170
|
+
if (!legacy.definition)
|
|
171
|
+
throw new Error(legacy.errors.join('\n'));
|
|
172
|
+
return {
|
|
173
|
+
definition: legacy.definition,
|
|
174
|
+
exists: true,
|
|
175
|
+
sourcePath: legacy.sourcePath,
|
|
176
|
+
provenance: 'legacy',
|
|
177
|
+
warnings: [],
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
const managed = await loadManagedConfig(projectDir);
|
|
181
|
+
const discovery = await discoverProjectLocalBundles(projectDir);
|
|
182
|
+
if (discovery.errors.length > 0) {
|
|
183
|
+
throw new Error(discovery.errors.map(error => `${error.path}: ${error.field}: ${error.actual}`).join('\n'));
|
|
184
|
+
}
|
|
185
|
+
if (!managed.config && discovery.bundles.length === 0) {
|
|
186
|
+
return { exists: false, sourcePath: managed.path, provenance: 'managed', warnings: [] };
|
|
187
|
+
}
|
|
188
|
+
const config = managed.config ?? ProjectQuickrefConfigSchema.parse({ version: '1' });
|
|
189
|
+
const excluded = new Set(config.discovery.excludeBundles.map(id => id.toLowerCase()));
|
|
190
|
+
const warnings = [];
|
|
191
|
+
const candidates = [];
|
|
192
|
+
if (config.discovery.enabled) {
|
|
193
|
+
for (const bundle of discovery.bundles) {
|
|
194
|
+
if (excluded.has(bundle.id.toLowerCase()))
|
|
195
|
+
continue;
|
|
196
|
+
const override = config.discovery.overrides[bundle.id];
|
|
197
|
+
if (override?.hidden)
|
|
198
|
+
continue;
|
|
199
|
+
const artifacts = (await enumerateBundleArtifacts(bundle.artifactPath ?? bundle.bundlePath))
|
|
200
|
+
.sort((a, b) => a.type.localeCompare(b.type) || a.id.localeCompare(b.id));
|
|
201
|
+
const inferredShow = dedupeShow(artifacts.map(artifact => ({
|
|
202
|
+
type: artifact.type,
|
|
203
|
+
name: artifact.id,
|
|
204
|
+
}))).slice(0, 8);
|
|
205
|
+
if (artifacts.length > inferredShow.length && inferredShow.length === 8) {
|
|
206
|
+
warnings.push(`${bundle.id}: show hints truncated from ${artifacts.length} artifacts to 8`);
|
|
207
|
+
}
|
|
208
|
+
let discover = override?.discover ?? dedupe([
|
|
209
|
+
bundle.id,
|
|
210
|
+
bundle.manifest.name,
|
|
211
|
+
...(bundle.manifest.keywords ?? []),
|
|
212
|
+
]).slice(0, 3);
|
|
213
|
+
const show = override?.show ? dedupeShow(override.show) : inferredShow;
|
|
214
|
+
if (discover.length === 0 && show.length === 0)
|
|
215
|
+
discover = [bundle.id];
|
|
216
|
+
candidates.push({
|
|
217
|
+
order: override?.order ?? 0,
|
|
218
|
+
type: bundle.type,
|
|
219
|
+
id: bundle.id,
|
|
220
|
+
entry: {
|
|
221
|
+
title: override?.title ?? bundle.manifest.name,
|
|
222
|
+
summary: override?.summary ?? bundle.manifest.description,
|
|
223
|
+
discover,
|
|
224
|
+
show,
|
|
225
|
+
},
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
candidates.sort((a, b) => a.order - b.order || a.type.localeCompare(b.type) || a.id.localeCompare(b.id));
|
|
230
|
+
const discoveredEntries = candidates.map(candidate => candidate.entry);
|
|
231
|
+
const entries = [...discoveredEntries, ...config.entries];
|
|
232
|
+
if (entries.length > 50)
|
|
233
|
+
warnings.push(`quickref entries truncated from ${entries.length} to 50`);
|
|
234
|
+
const bounded = entries.slice(0, 50);
|
|
235
|
+
if (bounded.length === 0) {
|
|
236
|
+
return { exists: false, sourcePath: managed.path, provenance: 'managed', warnings };
|
|
237
|
+
}
|
|
238
|
+
return {
|
|
239
|
+
definition: {
|
|
240
|
+
version: '1',
|
|
241
|
+
project: config.project ?? await inferredProject(projectDir),
|
|
242
|
+
precedence: config.precedence ?? 'Use project-local capabilities before generic AIWG workflows when they apply.',
|
|
243
|
+
entries: bounded,
|
|
244
|
+
},
|
|
245
|
+
exists: true,
|
|
246
|
+
sourcePath: managed.path,
|
|
247
|
+
provenance: 'managed',
|
|
248
|
+
warnings,
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
export async function hasProjectQuickref(projectDir) {
|
|
252
|
+
return (await resolveProjectQuickref(projectDir)).exists;
|
|
253
|
+
}
|
|
81
254
|
export function renderProjectQuickref(definition) {
|
|
82
255
|
const skillName = projectQuickrefSkillName(definition.project.id);
|
|
83
256
|
const lines = [
|
|
@@ -111,11 +284,9 @@ export function renderProjectQuickref(definition) {
|
|
|
111
284
|
return lines.join('\n');
|
|
112
285
|
}
|
|
113
286
|
export async function generateProjectQuickref(projectDir, options = {}) {
|
|
114
|
-
const loaded = await
|
|
115
|
-
if (!loaded.exists)
|
|
287
|
+
const loaded = await resolveProjectQuickref(projectDir);
|
|
288
|
+
if (!loaded.exists || !loaded.definition)
|
|
116
289
|
throw new Error(`Project quickref source not found: ${loaded.sourcePath}`);
|
|
117
|
-
if (!loaded.definition)
|
|
118
|
-
throw new Error(loaded.errors.join('\n'));
|
|
119
290
|
if (!options.dryRun)
|
|
120
291
|
await appendAiwgSourceTrackBlock(projectDir);
|
|
121
292
|
const skillName = projectQuickrefSkillName(loaded.definition.project.id);
|
|
@@ -127,6 +298,14 @@ export async function generateProjectQuickref(projectDir, options = {}) {
|
|
|
127
298
|
await mkdir(dirname(outputPath), { recursive: true });
|
|
128
299
|
await writeFile(outputPath, content, 'utf8');
|
|
129
300
|
}
|
|
301
|
+
if (!options.dryRun && loaded.provenance === 'managed') {
|
|
302
|
+
const snapshotPath = projectAiwgPath(projectDir, 'generated', 'project-quickref', 'definition.json');
|
|
303
|
+
const snapshot = JSON.stringify(loaded.definition, null, 2) + '\n';
|
|
304
|
+
if (await readIfPresent(snapshotPath) !== snapshot) {
|
|
305
|
+
await mkdir(dirname(snapshotPath), { recursive: true });
|
|
306
|
+
await writeFile(snapshotPath, snapshot, 'utf8');
|
|
307
|
+
}
|
|
308
|
+
}
|
|
130
309
|
if (!options.dryRun) {
|
|
131
310
|
const generatedRoot = projectAiwgPath(projectDir, 'generated', 'project-quickref');
|
|
132
311
|
try {
|
|
@@ -149,6 +328,8 @@ export async function generateProjectQuickref(projectDir, options = {}) {
|
|
|
149
328
|
content,
|
|
150
329
|
changed,
|
|
151
330
|
dryRun: options.dryRun ?? false,
|
|
331
|
+
provenance: loaded.provenance,
|
|
332
|
+
warnings: loaded.warnings,
|
|
152
333
|
};
|
|
153
334
|
}
|
|
154
335
|
function resolveProviderSkillsRoot(provider, projectDir, homeDir) {
|
|
@@ -206,9 +387,9 @@ async function findStaleOwnedQuickrefs(root, projectDir, keepName, global) {
|
|
|
206
387
|
}
|
|
207
388
|
export async function deployProjectQuickref(projectDir, provider, options = {}) {
|
|
208
389
|
const generated = await generateProjectQuickref(projectDir, { dryRun: options.dryRun });
|
|
209
|
-
const loaded = await
|
|
390
|
+
const loaded = await resolveProjectQuickref(projectDir);
|
|
210
391
|
if (!loaded.definition)
|
|
211
|
-
throw new Error(loaded.
|
|
392
|
+
throw new Error(`Project quickref source not found: ${loaded.sourcePath}`);
|
|
212
393
|
const target = resolveProviderSkillsRoot(provider, projectDir, options.homeDir ?? homedir());
|
|
213
394
|
const targetDir = join(target.root, generated.skillName);
|
|
214
395
|
const targetPath = join(targetDir, 'SKILL.md');
|
|
@@ -259,11 +440,17 @@ export async function deployProjectQuickref(projectDir, provider, options = {})
|
|
|
259
440
|
};
|
|
260
441
|
}
|
|
261
442
|
export async function auditProjectQuickref(projectDir, providers, options = {}) {
|
|
262
|
-
|
|
443
|
+
let loaded;
|
|
444
|
+
try {
|
|
445
|
+
loaded = await resolveProjectQuickref(projectDir);
|
|
446
|
+
}
|
|
447
|
+
catch (error) {
|
|
448
|
+
return { exists: true, errors: [error.message], drift: [] };
|
|
449
|
+
}
|
|
263
450
|
if (!loaded.exists)
|
|
264
|
-
return { exists: false, errors:
|
|
451
|
+
return { exists: false, errors: [], drift: [] };
|
|
265
452
|
if (!loaded.definition)
|
|
266
|
-
return { exists: true, errors:
|
|
453
|
+
return { exists: true, errors: ['project quickref definition unavailable'], drift: [] };
|
|
267
454
|
const content = renderProjectQuickref(loaded.definition);
|
|
268
455
|
const skillName = projectQuickrefSkillName(loaded.definition.project.id);
|
|
269
456
|
const generatedPath = projectAiwgPath(projectDir, 'generated', 'project-quickref', skillName, 'SKILL.md');
|
package/package.json
CHANGED
|
@@ -657,6 +657,13 @@ for (const [id, sourceRoot] of MANIFEST_PLUGIN_SOURCES) {
|
|
|
657
657
|
keywords: manifest.keywords || manifest.tags || [],
|
|
658
658
|
category: manifest.category || 'productivity',
|
|
659
659
|
sourceRoot,
|
|
660
|
+
declaredSkills: manifest.skills ?? [],
|
|
661
|
+
// Plugin payloads run outside the checkout. Rewrite references back to
|
|
662
|
+
// their packaged root, including the pre-rename Ralph path retained by
|
|
663
|
+
// older Agent Loop documentation.
|
|
664
|
+
selfReferenceRoots: id === 'agent-loop'
|
|
665
|
+
? [sourceRoot, 'agentic/code/addons/ralph']
|
|
666
|
+
: [sourceRoot],
|
|
660
667
|
};
|
|
661
668
|
}
|
|
662
669
|
|
|
@@ -774,6 +781,48 @@ function copyDir(src, dest, dryRun = false, filter = null) {
|
|
|
774
781
|
return copied;
|
|
775
782
|
}
|
|
776
783
|
|
|
784
|
+
function rewritePackagedSelfReferences(pluginDir, roots = []) {
|
|
785
|
+
if (roots.length === 0) return;
|
|
786
|
+
const pending = [pluginDir];
|
|
787
|
+
while (pending.length > 0) {
|
|
788
|
+
const current = pending.pop();
|
|
789
|
+
for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
|
|
790
|
+
const target = path.join(current, entry.name);
|
|
791
|
+
if (entry.isDirectory()) {
|
|
792
|
+
pending.push(target);
|
|
793
|
+
} else if (entry.isFile() && /\.(?:md|json|ya?ml)$/.test(entry.name)) {
|
|
794
|
+
const original = fs.readFileSync(target, 'utf8');
|
|
795
|
+
let rewritten = original;
|
|
796
|
+
for (const root of roots) {
|
|
797
|
+
rewritten = rewritten.replaceAll(`${root}/`, '${CLAUDE_PLUGIN_ROOT}/');
|
|
798
|
+
}
|
|
799
|
+
if (rewritten !== original) fs.writeFileSync(target, rewritten, 'utf8');
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
function pruneUndeclaredPackagedSkills(pluginDir, declaredSkills) {
|
|
806
|
+
if (!declaredSkills) return;
|
|
807
|
+
const skillsRoot = path.join(pluginDir, 'skills');
|
|
808
|
+
if (!fs.existsSync(skillsRoot)) return;
|
|
809
|
+
const allowed = new Set(declaredSkills);
|
|
810
|
+
for (const entry of fs.readdirSync(skillsRoot, { withFileTypes: true })) {
|
|
811
|
+
if (entry.isDirectory() && !allowed.has(entry.name)) {
|
|
812
|
+
fs.rmSync(path.join(skillsRoot, entry.name), { recursive: true, force: true });
|
|
813
|
+
} else if (entry.isFile() && entry.name.endsWith('.md')) {
|
|
814
|
+
const skillId = path.basename(entry.name, '.md');
|
|
815
|
+
if (allowed.has(skillId)) {
|
|
816
|
+
const skillDir = path.join(skillsRoot, skillId);
|
|
817
|
+
fs.mkdirSync(skillDir, { recursive: true });
|
|
818
|
+
fs.renameSync(path.join(skillsRoot, entry.name), path.join(skillDir, 'SKILL.md'));
|
|
819
|
+
} else {
|
|
820
|
+
fs.rmSync(path.join(skillsRoot, entry.name), { force: true });
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
|
|
777
826
|
// Clean plugin directory (except .claude-plugin)
|
|
778
827
|
function cleanPlugin(pluginDir) {
|
|
779
828
|
if (!fs.existsSync(pluginDir)) return;
|
|
@@ -811,6 +860,10 @@ function packagePlugin(name, config, options) {
|
|
|
811
860
|
console.log(` 📁 Copying self-contained source from ${config.sourceRoot}...`);
|
|
812
861
|
const count = copyDir(config.sourceRoot, pluginDir, options.dryRun);
|
|
813
862
|
console.log(` ${count} files`);
|
|
863
|
+
if (!options.dryRun) {
|
|
864
|
+
rewritePackagedSelfReferences(pluginDir, config.selfReferenceRoots);
|
|
865
|
+
pruneUndeclaredPackagedSkills(pluginDir, config.declaredSkills);
|
|
866
|
+
}
|
|
814
867
|
}
|
|
815
868
|
|
|
816
869
|
// Copy sources
|