vigiles 4.0.2 → 4.1.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/dist/adapter-registry.d.ts +45 -1
- package/dist/adapter-registry.js +78 -3
- package/dist/cli.js +125 -21
- package/dist/core/types.d.ts +10 -0
- package/dist/setup-plan.d.ts +11 -0
- package/dist/setup-plan.js +27 -0
- package/dist/skill-harness.d.ts +25 -0
- package/dist/skill-harness.js +40 -0
- package/package.json +1 -1
|
@@ -31,12 +31,56 @@ export interface DetectResult {
|
|
|
31
31
|
export declare function detectAdapterResult(root: string): DetectResult;
|
|
32
32
|
/** The detected adapter (highest specificity), else the default (Claude Code). */
|
|
33
33
|
export declare function detectAdapter(root: string): HarnessAdapter;
|
|
34
|
-
/**
|
|
34
|
+
/** Lower-case, trim, and map a short alias to its canonical adapter name. */
|
|
35
|
+
export declare function normalizeHarnessName(name: string): string;
|
|
36
|
+
/** Look up a registered adapter by `name` (alias-aware, e.g. `claude`). */
|
|
35
37
|
export declare function getAdapter(name: string): HarnessAdapter | undefined;
|
|
38
|
+
/**
|
|
39
|
+
* The adapter whose instruction file is `filename` (e.g. `AGENTS.md` → codex,
|
|
40
|
+
* `CLAUDE.md` → claude-code), if any. The per-spec disambiguation signal: a
|
|
41
|
+
* `<file>.spec.ts` compiles a `<file>` instruction file, so the filename names
|
|
42
|
+
* the harness more specifically than config/detect for THAT spec.
|
|
43
|
+
*/
|
|
44
|
+
export declare function adapterForInstructionFile(filename: string): HarnessAdapter | undefined;
|
|
36
45
|
/**
|
|
37
46
|
* Resolve the adapter for a command: an explicit `--harness <name>` wins (throws
|
|
38
47
|
* if unknown); otherwise auto-detect from `root`. The single entry point the CLI
|
|
39
48
|
* uses so detection + override live in one place.
|
|
40
49
|
*/
|
|
41
50
|
export declare function resolveAdapter(root: string, harness?: string): HarnessAdapter;
|
|
51
|
+
/** Normalize a config `harness` value (string | string[]) to a canonical list. */
|
|
52
|
+
export declare function normalizeHarnessList(harness?: string | readonly string[]): string[];
|
|
53
|
+
/**
|
|
54
|
+
* The adapter chosen for a single-dialect operation. A discriminated union so an
|
|
55
|
+
* invalid state — a "notice" with no message, or a clean pick carrying a stray
|
|
56
|
+
* string — is unrepresentable: `kind: "ok"` has no `notice`, `kind: "notice"`
|
|
57
|
+
* always carries a non-empty one. Both variants carry the `adapter`.
|
|
58
|
+
*/
|
|
59
|
+
export type HarnessSelection = {
|
|
60
|
+
readonly kind: "ok";
|
|
61
|
+
readonly adapter: HarnessAdapter;
|
|
62
|
+
} | {
|
|
63
|
+
readonly kind: "notice";
|
|
64
|
+
readonly adapter: HarnessAdapter;
|
|
65
|
+
readonly notice: string;
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* Resolve the single harness a compile/lint operation should use, with explicit
|
|
69
|
+
* precedence — the deterministic replacement for sniffing the cwd:
|
|
70
|
+
*
|
|
71
|
+
* 1. `--harness=` flag (wins; throws if unknown).
|
|
72
|
+
* 2. config `harness` resolving to a single entry → use it.
|
|
73
|
+
* 3. config `harness` with multiple entries → use the first, with a loud notice.
|
|
74
|
+
* 4. no config → auto-detect, with a loud notice when the repo is ambiguous.
|
|
75
|
+
*
|
|
76
|
+
* `configHarness` is parsed once (alias-normalized) at the call site and passed
|
|
77
|
+
* in; this function re-normalizes idempotently so it's safe either way. Pure
|
|
78
|
+
* (besides reading `root`'s layout for detection) so the precedence is
|
|
79
|
+
* unit-testable without a real compile. See research/multi-harness-compile.md.
|
|
80
|
+
*/
|
|
81
|
+
export declare function resolveHarnessSelection(opts: {
|
|
82
|
+
root: string;
|
|
83
|
+
flag?: string;
|
|
84
|
+
configHarness?: string | readonly string[];
|
|
85
|
+
}): HarnessSelection;
|
|
42
86
|
//# sourceMappingURL=adapter-registry.d.ts.map
|
package/dist/adapter-registry.js
CHANGED
|
@@ -3,8 +3,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.ADAPTERS = exports.defaultAdapter = void 0;
|
|
4
4
|
exports.detectAdapterResult = detectAdapterResult;
|
|
5
5
|
exports.detectAdapter = detectAdapter;
|
|
6
|
+
exports.normalizeHarnessName = normalizeHarnessName;
|
|
6
7
|
exports.getAdapter = getAdapter;
|
|
8
|
+
exports.adapterForInstructionFile = adapterForInstructionFile;
|
|
7
9
|
exports.resolveAdapter = resolveAdapter;
|
|
10
|
+
exports.normalizeHarnessList = normalizeHarnessList;
|
|
11
|
+
exports.resolveHarnessSelection = resolveHarnessSelection;
|
|
8
12
|
const adapter_js_1 = require("./adapters/claude-code/adapter.js");
|
|
9
13
|
const adapter_js_2 = require("./adapters/codex/adapter.js");
|
|
10
14
|
/** The default adapter when detection finds no harness markers. */
|
|
@@ -32,9 +36,32 @@ function detectAdapterResult(root) {
|
|
|
32
36
|
function detectAdapter(root) {
|
|
33
37
|
return detectAdapterResult(root).adapter;
|
|
34
38
|
}
|
|
35
|
-
/**
|
|
39
|
+
/**
|
|
40
|
+
* Short-name aliases accepted anywhere a harness name is supplied (config,
|
|
41
|
+
* `--harness=`). `init` historically uses `"claude"`; the canonical adapter name
|
|
42
|
+
* is `"claude-code"`. Normalizing here keeps selection and the registry in sync.
|
|
43
|
+
*/
|
|
44
|
+
const HARNESS_ALIASES = {
|
|
45
|
+
claude: "claude-code",
|
|
46
|
+
};
|
|
47
|
+
/** Lower-case, trim, and map a short alias to its canonical adapter name. */
|
|
48
|
+
function normalizeHarnessName(name) {
|
|
49
|
+
const n = name.trim().toLowerCase();
|
|
50
|
+
return HARNESS_ALIASES[n] ?? n;
|
|
51
|
+
}
|
|
52
|
+
/** Look up a registered adapter by `name` (alias-aware, e.g. `claude`). */
|
|
36
53
|
function getAdapter(name) {
|
|
37
|
-
|
|
54
|
+
const canonical = normalizeHarnessName(name);
|
|
55
|
+
return exports.ADAPTERS.find((a) => a.name === canonical);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* The adapter whose instruction file is `filename` (e.g. `AGENTS.md` → codex,
|
|
59
|
+
* `CLAUDE.md` → claude-code), if any. The per-spec disambiguation signal: a
|
|
60
|
+
* `<file>.spec.ts` compiles a `<file>` instruction file, so the filename names
|
|
61
|
+
* the harness more specifically than config/detect for THAT spec.
|
|
62
|
+
*/
|
|
63
|
+
function adapterForInstructionFile(filename) {
|
|
64
|
+
return exports.ADAPTERS.find((a) => a.layout.instructionFile === filename);
|
|
38
65
|
}
|
|
39
66
|
/**
|
|
40
67
|
* Resolve the adapter for a command: an explicit `--harness <name>` wins (throws
|
|
@@ -42,7 +69,7 @@ function getAdapter(name) {
|
|
|
42
69
|
* uses so detection + override live in one place.
|
|
43
70
|
*/
|
|
44
71
|
function resolveAdapter(root, harness) {
|
|
45
|
-
if (harness !== undefined) {
|
|
72
|
+
if (harness !== undefined && harness !== "") {
|
|
46
73
|
const a = getAdapter(harness);
|
|
47
74
|
if (!a) {
|
|
48
75
|
const known = exports.ADAPTERS.map((x) => x.name).join(", ");
|
|
@@ -52,4 +79,52 @@ function resolveAdapter(root, harness) {
|
|
|
52
79
|
}
|
|
53
80
|
return detectAdapter(root);
|
|
54
81
|
}
|
|
82
|
+
/** Normalize a config `harness` value (string | string[]) to a canonical list. */
|
|
83
|
+
function normalizeHarnessList(harness) {
|
|
84
|
+
if (harness === undefined)
|
|
85
|
+
return [];
|
|
86
|
+
const arr = Array.isArray(harness) ? harness : [harness];
|
|
87
|
+
return arr.map(normalizeHarnessName).filter(Boolean);
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Resolve the single harness a compile/lint operation should use, with explicit
|
|
91
|
+
* precedence — the deterministic replacement for sniffing the cwd:
|
|
92
|
+
*
|
|
93
|
+
* 1. `--harness=` flag (wins; throws if unknown).
|
|
94
|
+
* 2. config `harness` resolving to a single entry → use it.
|
|
95
|
+
* 3. config `harness` with multiple entries → use the first, with a loud notice.
|
|
96
|
+
* 4. no config → auto-detect, with a loud notice when the repo is ambiguous.
|
|
97
|
+
*
|
|
98
|
+
* `configHarness` is parsed once (alias-normalized) at the call site and passed
|
|
99
|
+
* in; this function re-normalizes idempotently so it's safe either way. Pure
|
|
100
|
+
* (besides reading `root`'s layout for detection) so the precedence is
|
|
101
|
+
* unit-testable without a real compile. See research/multi-harness-compile.md.
|
|
102
|
+
*/
|
|
103
|
+
function resolveHarnessSelection(opts) {
|
|
104
|
+
const { root, flag, configHarness } = opts;
|
|
105
|
+
if (flag !== undefined && flag !== "") {
|
|
106
|
+
return { kind: "ok", adapter: resolveAdapter(root, flag) };
|
|
107
|
+
}
|
|
108
|
+
const list = normalizeHarnessList(configHarness);
|
|
109
|
+
if (list.length === 1) {
|
|
110
|
+
return { kind: "ok", adapter: resolveAdapter(root, list[0]) };
|
|
111
|
+
}
|
|
112
|
+
if (list.length > 1) {
|
|
113
|
+
const adapter = resolveAdapter(root, list[0]);
|
|
114
|
+
return {
|
|
115
|
+
kind: "notice",
|
|
116
|
+
adapter,
|
|
117
|
+
notice: `repo targets ${list.join(", ")} — compiling for ${adapter.name}; override with --harness=`,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
const det = detectAdapterResult(root);
|
|
121
|
+
if (det.ambiguousWith.length > 0) {
|
|
122
|
+
return {
|
|
123
|
+
kind: "notice",
|
|
124
|
+
adapter: det.adapter,
|
|
125
|
+
notice: `repo matches ${[det.adapter.name, ...det.ambiguousWith].join(", ")} — set "harness" in .vigilesrc.json or use --harness=`,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
return { kind: "ok", adapter: det.adapter };
|
|
129
|
+
}
|
|
55
130
|
//# sourceMappingURL=adapter-registry.js.map
|
package/dist/cli.js
CHANGED
|
@@ -21,9 +21,9 @@ const types_js_1 = require("./core/types.js");
|
|
|
21
21
|
const test_coverage_js_1 = require("./test-coverage.js");
|
|
22
22
|
const scan_js_1 = require("./scan.js");
|
|
23
23
|
const adapter_registry_js_1 = require("./adapter-registry.js");
|
|
24
|
+
const skill_harness_js_1 = require("./skill-harness.js");
|
|
24
25
|
const leaderboard_js_1 = require("./leaderboard.js");
|
|
25
26
|
const compile_js_1 = require("./core/compile.js");
|
|
26
|
-
const dialect_js_1 = require("./adapters/claude-code/dialect.js");
|
|
27
27
|
const proofs_js_1 = require("./core/proofs.js");
|
|
28
28
|
const inline_js_1 = require("./core/inline.js");
|
|
29
29
|
const frontmatter_js_1 = require("./core/frontmatter.js");
|
|
@@ -140,12 +140,12 @@ function compileGeneratorSkillToFile(specPath, source) {
|
|
|
140
140
|
return false;
|
|
141
141
|
}
|
|
142
142
|
/** Compile a ClaudeSpec → its primary + any additional targets. */
|
|
143
|
-
function compileClaudeToFile(spec, specPath, config) {
|
|
143
|
+
function compileClaudeToFile(spec, specPath, config, dialect) {
|
|
144
144
|
const basePath = process.cwd();
|
|
145
145
|
const { markdown, errors, linterResults, targets } = (0, compile_js_1.compileClaude)(spec, {
|
|
146
146
|
basePath,
|
|
147
147
|
specFile: specPath,
|
|
148
|
-
dialect
|
|
148
|
+
dialect,
|
|
149
149
|
maxRules: config.maxRules,
|
|
150
150
|
maxTokens: config.maxTokens,
|
|
151
151
|
maxSectionLines: config.maxSectionLines,
|
|
@@ -175,15 +175,54 @@ function compileClaudeToFile(spec, specPath, config) {
|
|
|
175
175
|
console.log(` ${String(Object.keys(spec.rules).length)} rules (${String(linterCount)} linter-verified)`);
|
|
176
176
|
return true;
|
|
177
177
|
}
|
|
178
|
+
/**
|
|
179
|
+
* Branch 3 of the mirror story (research/multi-harness-compile.md): when a repo
|
|
180
|
+
* declares ≥2 harnesses and nothing else fans out the instruction file, write a
|
|
181
|
+
* byte-identical copy to each other harness's instruction file (e.g. CLAUDE.md →
|
|
182
|
+
* AGENTS.md). A copy — not a symlink — because it works everywhere and carries
|
|
183
|
+
* the source's embedded integrity hash by construction, so a hand-edit of the
|
|
184
|
+
* mirror trips the existing `integrity` check. Never fights a sync tool or
|
|
185
|
+
* clobbers a target that owns its own spec.
|
|
186
|
+
*/
|
|
187
|
+
function writeInstructionMirrors(primaryOutput, harnesses) {
|
|
188
|
+
if (harnesses.length < 2)
|
|
189
|
+
return;
|
|
190
|
+
const cwd = process.cwd();
|
|
191
|
+
// A sync tool (Ruler/rulesync) owns fan-out — don't fight it.
|
|
192
|
+
if ((0, compose_js_1.detectSyncTools)(cwd).length > 0)
|
|
193
|
+
return;
|
|
194
|
+
const primaryName = (0, node_path_1.basename)(primaryOutput);
|
|
195
|
+
const primaryAbs = (0, node_path_1.resolve)(cwd, primaryOutput);
|
|
196
|
+
if (!(0, node_fs_1.existsSync)(primaryAbs))
|
|
197
|
+
return;
|
|
198
|
+
const content = (0, node_fs_1.readFileSync)(primaryAbs, "utf-8");
|
|
199
|
+
for (const name of harnesses) {
|
|
200
|
+
const adapter = (0, adapter_registry_js_1.getAdapter)(name);
|
|
201
|
+
if (!adapter)
|
|
202
|
+
continue;
|
|
203
|
+
const target = adapter.layout.instructionFile;
|
|
204
|
+
if (target === primaryName)
|
|
205
|
+
continue; // the file we just compiled
|
|
206
|
+
// Never clobber a target that has its own spec (a genuinely separate file).
|
|
207
|
+
if ((0, node_fs_1.existsSync)((0, node_path_1.resolve)(cwd, `${target}.spec.ts`)))
|
|
208
|
+
continue;
|
|
209
|
+
const targetAbs = (0, node_path_1.resolve)(cwd, target);
|
|
210
|
+
if ((0, node_fs_1.existsSync)(targetAbs) && (0, node_fs_1.readFileSync)(targetAbs, "utf-8") === content) {
|
|
211
|
+
continue; // already byte-identical
|
|
212
|
+
}
|
|
213
|
+
(0, node_fs_1.writeFileSync)(targetAbs, content);
|
|
214
|
+
console.log(` ↳ mirrored ${primaryName} → ${target} (byte-identical)`);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
178
217
|
/** Compile a declarative SkillSpec → SKILL.md. */
|
|
179
|
-
function compileSkillToFile(spec, specPath) {
|
|
218
|
+
function compileSkillToFile(spec, specPath, dialect) {
|
|
180
219
|
const outputPath = specPath.replace(/\.spec\.ts$/, "");
|
|
181
220
|
const { markdown, errors } = (0, compile_js_1.compileSkill)(spec, {
|
|
182
221
|
basePath: process.cwd(),
|
|
183
222
|
specFile: specPath,
|
|
184
|
-
//
|
|
223
|
+
// The SKILL.md frontmatter profile comes from the resolved harness — a Codex
|
|
185
224
|
// repo gets a minimal (name + description) SKILL.md; CC gets the full set.
|
|
186
|
-
dialect
|
|
225
|
+
dialect,
|
|
187
226
|
});
|
|
188
227
|
(0, node_fs_1.writeFileSync)((0, node_path_1.resolve)(process.cwd(), outputPath), markdown);
|
|
189
228
|
if (errors.length === 0) {
|
|
@@ -195,12 +234,12 @@ function compileSkillToFile(spec, specPath) {
|
|
|
195
234
|
return false;
|
|
196
235
|
}
|
|
197
236
|
/** Compile a subagent spec → agents/<name>.md (with its result-contract section). */
|
|
198
|
-
function compileAgentToFile(spec, specPath) {
|
|
237
|
+
function compileAgentToFile(spec, specPath, dialect) {
|
|
199
238
|
const outputPath = specPath.replace(/\.spec\.ts$/, "");
|
|
200
239
|
const { markdown, errors } = (0, compile_js_1.compileAgent)(spec, {
|
|
201
240
|
basePath: process.cwd(),
|
|
202
241
|
specFile: specPath,
|
|
203
|
-
dialect
|
|
242
|
+
dialect,
|
|
204
243
|
});
|
|
205
244
|
(0, node_fs_1.writeFileSync)((0, node_path_1.resolve)(process.cwd(), outputPath), markdown);
|
|
206
245
|
if (errors.length === 0) {
|
|
@@ -241,8 +280,21 @@ async function collectAgentNames() {
|
|
|
241
280
|
}
|
|
242
281
|
return names;
|
|
243
282
|
}
|
|
244
|
-
async function compile(specPaths, config) {
|
|
283
|
+
async function compile(specPaths, config, opts = {}) {
|
|
245
284
|
let allValid = true;
|
|
285
|
+
// Parse the declared harness set ONCE (alias-normalized) and feed both the
|
|
286
|
+
// dialect pick and the mirror from it — no re-parsing, no cwd-sniffing in the
|
|
287
|
+
// helpers. A loud notice (never a silent guess) on a multi-harness or
|
|
288
|
+
// ambiguous-detection pick.
|
|
289
|
+
const declaredHarnesses = (0, adapter_registry_js_1.normalizeHarnessList)(config.harness);
|
|
290
|
+
const selection = (0, adapter_registry_js_1.resolveHarnessSelection)({
|
|
291
|
+
root: process.cwd(),
|
|
292
|
+
flag: opts.harnessFlag,
|
|
293
|
+
configHarness: declaredHarnesses,
|
|
294
|
+
});
|
|
295
|
+
if (selection.kind === "notice")
|
|
296
|
+
console.log(`⚠ ${selection.notice}`);
|
|
297
|
+
const dialect = selection.adapter.dialect;
|
|
246
298
|
// Resolved lazily on the first railway spec — every delegate() target is
|
|
247
299
|
// checked against the agents defined anywhere in the project.
|
|
248
300
|
let knownAgents = null;
|
|
@@ -262,15 +314,36 @@ async function compile(specPaths, config) {
|
|
|
262
314
|
continue;
|
|
263
315
|
}
|
|
264
316
|
if (spec._specType === "claude") {
|
|
265
|
-
|
|
317
|
+
// Spec-target disambiguation: a CLAUDE.md.spec.ts is a claude-code file, an
|
|
318
|
+
// AGENTS.md.spec.ts a codex one — the strongest dialect signal for THIS
|
|
319
|
+
// spec. The flag still overrides; absent one, the spec's own target wins
|
|
320
|
+
// over config/detect. (Skill/agent targets don't name a harness, so they
|
|
321
|
+
// keep the run-level dialect.)
|
|
322
|
+
const targetFile = (0, node_path_1.basename)(specPath).replace(/\.spec\.ts$/, "");
|
|
323
|
+
const specDialect = opts.harnessFlag === undefined
|
|
324
|
+
? ((0, adapter_registry_js_1.adapterForInstructionFile)(targetFile)?.dialect ?? dialect)
|
|
325
|
+
: dialect;
|
|
326
|
+
if (compileClaudeToFile(spec, specPath, config, specDialect)) {
|
|
327
|
+
writeInstructionMirrors(specPath.replace(/\.spec\.ts$/, ""), declaredHarnesses);
|
|
328
|
+
}
|
|
329
|
+
else {
|
|
266
330
|
allValid = false;
|
|
331
|
+
}
|
|
267
332
|
}
|
|
268
333
|
else if (spec._specType === "skill") {
|
|
269
|
-
|
|
334
|
+
// Cross-harness verify: flag CC-only frontmatter a declared minimal-profile
|
|
335
|
+
// harness (Codex/OpenCode) would silently drop.
|
|
336
|
+
const forHarnesses = declaredHarnesses.length > 0
|
|
337
|
+
? declaredHarnesses
|
|
338
|
+
: [selection.adapter.name];
|
|
339
|
+
for (const w of (0, skill_harness_js_1.skillFrontmatterDropWarnings)(spec, forHarnesses)) {
|
|
340
|
+
console.log(`⚠ ${w}`);
|
|
341
|
+
}
|
|
342
|
+
if (!compileSkillToFile(spec, specPath, dialect))
|
|
270
343
|
allValid = false;
|
|
271
344
|
}
|
|
272
345
|
else if (spec._specType === "agent") {
|
|
273
|
-
if (!compileAgentToFile(spec, specPath))
|
|
346
|
+
if (!compileAgentToFile(spec, specPath, dialect))
|
|
274
347
|
allValid = false;
|
|
275
348
|
}
|
|
276
349
|
else if (spec._specType === "railway") {
|
|
@@ -1735,16 +1808,44 @@ async function setup(args) {
|
|
|
1735
1808
|
console.log("\n Non-markdown agent configs detected. Use a sync tool to convert:");
|
|
1736
1809
|
console.log(" npm install -D rule-porter");
|
|
1737
1810
|
}
|
|
1738
|
-
//
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1811
|
+
// Project config — record the harness(es) so compile/lint select the dialect
|
|
1812
|
+
// deterministically (no cwd sniffing), plus strict rule severities on --strict.
|
|
1813
|
+
writeProjectConfig({ harnesses, strict, written });
|
|
1814
|
+
printSetupSummary({ plan, strict, targets, needsMigration, written });
|
|
1815
|
+
}
|
|
1816
|
+
/** Canonical, de-duplicated harness list → a config value (string when one). */
|
|
1817
|
+
function harnessConfigValue(harnesses) {
|
|
1818
|
+
const canon = [...new Set(harnesses.map(adapter_registry_js_1.normalizeHarnessName))];
|
|
1819
|
+
return canon.length === 1 ? canon[0] : canon;
|
|
1820
|
+
}
|
|
1821
|
+
/**
|
|
1822
|
+
* Merge the resolved harness(es) (and strict rule severities) into
|
|
1823
|
+
* `.vigilesrc.json` without clobbering existing keys — an existing `harness`
|
|
1824
|
+
* stays, a missing one is added, a malformed file is left untouched.
|
|
1825
|
+
*/
|
|
1826
|
+
function writeProjectConfig(opts) {
|
|
1827
|
+
const configPath = (0, node_path_1.resolve)(process.cwd(), ".vigilesrc.json");
|
|
1828
|
+
const existed = (0, node_fs_1.existsSync)(configPath);
|
|
1829
|
+
let existing = {};
|
|
1830
|
+
if (existed) {
|
|
1831
|
+
try {
|
|
1832
|
+
existing = JSON.parse((0, node_fs_1.readFileSync)(configPath, "utf-8"));
|
|
1833
|
+
}
|
|
1834
|
+
catch {
|
|
1835
|
+
return; // user-owned malformed config — never clobber it
|
|
1745
1836
|
}
|
|
1746
1837
|
}
|
|
1747
|
-
|
|
1838
|
+
const merged = (0, setup_plan_js_1.mergeProjectConfig)(existing, {
|
|
1839
|
+
harness: harnessConfigValue(opts.harnesses),
|
|
1840
|
+
strict: opts.strict,
|
|
1841
|
+
});
|
|
1842
|
+
if (!merged)
|
|
1843
|
+
return;
|
|
1844
|
+
(0, node_fs_1.writeFileSync)(configPath, JSON.stringify(merged, null, 2) + "\n");
|
|
1845
|
+
console.log(`✓ ${existed ? "Updated" : "Created"} .vigilesrc.json`);
|
|
1846
|
+
if (!opts.written.includes(".vigilesrc.json")) {
|
|
1847
|
+
opts.written.push(".vigilesrc.json");
|
|
1848
|
+
}
|
|
1748
1849
|
}
|
|
1749
1850
|
// ---------------------------------------------------------------------------
|
|
1750
1851
|
// Strengthen: guidance() → enforce() suggestions
|
|
@@ -2367,7 +2468,10 @@ async function main() {
|
|
|
2367
2468
|
console.log("Run `vigiles init` to create one.");
|
|
2368
2469
|
process.exit(0);
|
|
2369
2470
|
}
|
|
2370
|
-
const
|
|
2471
|
+
const harnessFlag = args
|
|
2472
|
+
.find((a) => a.startsWith("--harness="))
|
|
2473
|
+
?.slice("--harness=".length);
|
|
2474
|
+
const valid = await compile(specs, config, { harnessFlag });
|
|
2371
2475
|
console.log("");
|
|
2372
2476
|
if (valid) {
|
|
2373
2477
|
console.log("Compilation complete.");
|
package/dist/core/types.d.ts
CHANGED
|
@@ -127,6 +127,16 @@ export interface VigilesConfig {
|
|
|
127
127
|
}>;
|
|
128
128
|
/** Orphan-docs check configuration. Include/exclude globs, tsconfig-style. */
|
|
129
129
|
orphans?: OrphansConfig;
|
|
130
|
+
/**
|
|
131
|
+
* The harness(es) this repo targets — selects the compile dialect / skill
|
|
132
|
+
* frontmatter profile / instruction-file shape, instead of sniffing the cwd.
|
|
133
|
+
* A single name (`"codex"`) for the common single-harness repo, or an array
|
|
134
|
+
* (`["claude-code", "codex"]`) declaring the supported set. Written by
|
|
135
|
+
* `vigiles init`. Omitted → the CLI auto-detects (backwards-compatible).
|
|
136
|
+
* Canonical adapter names; `"claude"` is accepted as an alias for
|
|
137
|
+
* `"claude-code"`. See research/multi-harness-compile.md.
|
|
138
|
+
*/
|
|
139
|
+
harness?: string | string[];
|
|
130
140
|
}
|
|
131
141
|
/** Valid marker types for rule detection. */
|
|
132
142
|
export type MarkerType = "headings" | "checkboxes";
|
package/dist/setup-plan.d.ts
CHANGED
|
@@ -44,6 +44,17 @@ export interface ParsedSetupArgs {
|
|
|
44
44
|
export declare function parseSetupArgs(args: readonly string[]): ParsedSetupArgs;
|
|
45
45
|
/** The non-interactive defaults: both pillars, CI, and the plugin. */
|
|
46
46
|
export declare function defaultPlan(strict?: boolean): SetupPlan;
|
|
47
|
+
/**
|
|
48
|
+
* Pure config-merge for what `vigiles init` writes to `.vigilesrc.json`: record
|
|
49
|
+
* the `harness` if absent, add strict rule severities if `--strict`, NEVER
|
|
50
|
+
* clobber an existing key. Returns the merged config, or `null` when nothing
|
|
51
|
+
* changed (so the IO layer skips the write). The IO (read/parse/write + the
|
|
52
|
+
* malformed-file guard) stays in cli.ts.
|
|
53
|
+
*/
|
|
54
|
+
export declare function mergeProjectConfig(existing: Record<string, unknown>, opts: {
|
|
55
|
+
harness: string | string[];
|
|
56
|
+
strict: boolean;
|
|
57
|
+
}): Record<string, unknown> | null;
|
|
47
58
|
/**
|
|
48
59
|
* Whether to drop into interactive prompts: a human at a TTY who passed neither
|
|
49
60
|
* `--yes` nor an explicit `--target`, and who hasn't already pinned every choice
|
package/dist/setup-plan.js
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
13
|
exports.parseSetupArgs = parseSetupArgs;
|
|
14
14
|
exports.defaultPlan = defaultPlan;
|
|
15
|
+
exports.mergeProjectConfig = mergeProjectConfig;
|
|
15
16
|
exports.shouldPrompt = shouldPrompt;
|
|
16
17
|
exports.planPluginInstall = planPluginInstall;
|
|
17
18
|
exports.resolvePlan = resolvePlan;
|
|
@@ -52,6 +53,32 @@ function defaultPlan(strict = false) {
|
|
|
52
53
|
force: false,
|
|
53
54
|
};
|
|
54
55
|
}
|
|
56
|
+
/**
|
|
57
|
+
* Pure config-merge for what `vigiles init` writes to `.vigilesrc.json`: record
|
|
58
|
+
* the `harness` if absent, add strict rule severities if `--strict`, NEVER
|
|
59
|
+
* clobber an existing key. Returns the merged config, or `null` when nothing
|
|
60
|
+
* changed (so the IO layer skips the write). The IO (read/parse/write + the
|
|
61
|
+
* malformed-file guard) stays in cli.ts.
|
|
62
|
+
*/
|
|
63
|
+
function mergeProjectConfig(existing, opts) {
|
|
64
|
+
const config = { ...existing };
|
|
65
|
+
let changed = false;
|
|
66
|
+
if (config.harness === undefined) {
|
|
67
|
+
config.harness = opts.harness;
|
|
68
|
+
changed = true;
|
|
69
|
+
}
|
|
70
|
+
if (opts.strict) {
|
|
71
|
+
const rules = { ...config.rules };
|
|
72
|
+
for (const r of ["require-spec", "require-skill-spec"]) {
|
|
73
|
+
if (rules[r] === undefined) {
|
|
74
|
+
rules[r] = "error";
|
|
75
|
+
changed = true;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
config.rules = rules;
|
|
79
|
+
}
|
|
80
|
+
return changed ? config : null;
|
|
81
|
+
}
|
|
55
82
|
/**
|
|
56
83
|
* Whether to drop into interactive prompts: a human at a TTY who passed neither
|
|
57
84
|
* `--yes` nor an explicit `--target`, and who hasn't already pinned every choice
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-harness skill-frontmatter verification (slice 3 of
|
|
3
|
+
* research/multi-harness-compile.md, the *verify* half).
|
|
4
|
+
*
|
|
5
|
+
* A skill's `SKILL.md` references are harness-agnostic; the one harness-specific
|
|
6
|
+
* surface is the frontmatter PROFILE. The `claude-code` profile emits CC-only
|
|
7
|
+
* keys (`disable-model-invocation`, `argument-hint`); the `minimal` profile
|
|
8
|
+
* (Codex, OpenCode) omits them. So a skill that sets those keys, in a repo that
|
|
9
|
+
* also targets a minimal-profile harness, has a silent semantic gap: the
|
|
10
|
+
* constraint the author expressed won't take effect there.
|
|
11
|
+
*
|
|
12
|
+
* This reports that gap. It is ASSUMPTION-FREE — the minimal profile *drops* the
|
|
13
|
+
* keys, so the warning states a fact about vigiles's own output, not a guess
|
|
14
|
+
* about another tool's parser tolerance.
|
|
15
|
+
*/
|
|
16
|
+
import type { SkillSpec } from "./core/spec.js";
|
|
17
|
+
/** The Claude-Code-only frontmatter keys a skill spec would emit. */
|
|
18
|
+
export declare function claudeOnlyFrontmatterKeys(spec: SkillSpec): string[];
|
|
19
|
+
/**
|
|
20
|
+
* Warn for each declared harness whose `minimal` SKILL.md profile would DROP a
|
|
21
|
+
* skill's Claude-Code-only frontmatter. Empty when the skill uses no such keys or
|
|
22
|
+
* no declared harness is minimal-profile.
|
|
23
|
+
*/
|
|
24
|
+
export declare function skillFrontmatterDropWarnings(spec: SkillSpec, harnessNames: readonly string[]): string[];
|
|
25
|
+
//# sourceMappingURL=skill-harness.d.ts.map
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.claudeOnlyFrontmatterKeys = claudeOnlyFrontmatterKeys;
|
|
4
|
+
exports.skillFrontmatterDropWarnings = skillFrontmatterDropWarnings;
|
|
5
|
+
const adapter_registry_js_1 = require("./adapter-registry.js");
|
|
6
|
+
/** The Claude-Code-only frontmatter keys a skill spec would emit. */
|
|
7
|
+
function claudeOnlyFrontmatterKeys(spec) {
|
|
8
|
+
const keys = [];
|
|
9
|
+
if (spec.disableModelInvocation !== undefined) {
|
|
10
|
+
keys.push("disable-model-invocation");
|
|
11
|
+
}
|
|
12
|
+
if (spec.argumentHint || (spec.inputs && spec.inputs.length > 0)) {
|
|
13
|
+
keys.push("argument-hint");
|
|
14
|
+
}
|
|
15
|
+
return keys;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Warn for each declared harness whose `minimal` SKILL.md profile would DROP a
|
|
19
|
+
* skill's Claude-Code-only frontmatter. Empty when the skill uses no such keys or
|
|
20
|
+
* no declared harness is minimal-profile.
|
|
21
|
+
*/
|
|
22
|
+
function skillFrontmatterDropWarnings(spec, harnessNames) {
|
|
23
|
+
const ccKeys = claudeOnlyFrontmatterKeys(spec);
|
|
24
|
+
if (ccKeys.length === 0)
|
|
25
|
+
return [];
|
|
26
|
+
const warnings = [];
|
|
27
|
+
const seen = new Set();
|
|
28
|
+
for (const name of harnessNames) {
|
|
29
|
+
const adapter = (0, adapter_registry_js_1.getAdapter)(name);
|
|
30
|
+
if (!adapter || seen.has(adapter.name))
|
|
31
|
+
continue;
|
|
32
|
+
seen.add(adapter.name);
|
|
33
|
+
if (adapter.dialect.skillFrontmatter === "minimal") {
|
|
34
|
+
const one = ccKeys.length === 1;
|
|
35
|
+
warnings.push(`skill "${spec.name}": ${ccKeys.join(", ")} ${one ? "is" : "are"} Claude-Code-only — declared harness "${adapter.name}" drops ${one ? "it" : "them"}.`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return warnings;
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=skill-harness.js.map
|