@aiwg/cli 2026.10.3 → 2026.10.5
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/README.md +2 -2
- package/agentic/code/providers/model-capabilities.v1.json +96 -4
- package/bin/aiwg.mjs +50 -27
- package/dist/src/agents/grounding/knowledge-base.js +54 -6
- package/dist/src/artifacts/browser-export.js +6 -0
- package/dist/src/artifacts/checksum-manifest.js +1 -0
- package/dist/src/artifacts/citation-parser.js +173 -92
- package/dist/src/artifacts/citation-table-schema.js +59 -0
- package/dist/src/artifacts/cli.js +69 -18
- package/dist/src/artifacts/corpus-tools/snapshot.js +1 -1
- package/dist/src/artifacts/corpus-tools/vision-extract.js +2 -0
- package/dist/src/artifacts/corpus-views/ref-parser.js +3 -1
- package/dist/src/artifacts/corpus-views/renderers.js +3 -3
- package/dist/src/artifacts/discovery-eval.js +6 -4
- package/dist/src/artifacts/fortemi-core-query-adapter.js +36 -3
- package/dist/src/artifacts/fortemi-core-sync.js +166 -40
- package/dist/src/artifacts/framework-capabilities.js +171 -0
- package/dist/src/artifacts/framework-index-paths.js +73 -0
- package/dist/src/artifacts/framework-source-digest.js +75 -0
- package/dist/src/artifacts/index-builder.js +286 -131
- package/dist/src/artifacts/index-files.js +132 -18
- package/dist/src/artifacts/index-preparation.js +130 -0
- package/dist/src/artifacts/index-refresh.js +591 -0
- package/dist/src/artifacts/index-worker.mjs +11 -0
- package/dist/src/artifacts/prebuilt-build-lock.js +47 -4
- package/dist/src/artifacts/prebuilt-deployment.js +46 -0
- package/dist/src/artifacts/prebuilt-relations.js +80 -0
- package/dist/src/artifacts/query-engine.js +410 -88
- package/dist/src/artifacts/relations-builder.js +168 -0
- package/dist/src/artifacts/relations.js +148 -0
- package/dist/src/artifacts/stats.js +5 -2
- package/dist/src/artifacts/types.js +30 -10
- package/dist/src/artifacts/watcher.js +1 -1
- package/dist/src/auth/credential-store.js +2 -1
- package/dist/src/catalog/cli.d.mts +5 -0
- package/dist/src/channel/manager.d.mts +20 -0
- package/dist/src/channel/manager.mjs +19 -2
- package/dist/src/cli/command-log.js +2 -3
- package/dist/src/cli/handlers/artifacts.js +3 -0
- package/dist/src/cli/handlers/auth.js +3 -0
- package/dist/src/cli/handlers/decision.js +19 -12
- package/dist/src/cli/handlers/diagnose.js +4 -3
- package/dist/src/cli/handlers/effect.js +4 -2
- package/dist/src/cli/handlers/evidence.js +3 -0
- package/dist/src/cli/handlers/help.js +1 -0
- package/dist/src/cli/handlers/index.js +3 -1
- package/dist/src/cli/handlers/init.js +4 -1
- package/dist/src/cli/handlers/lint.js +5 -0
- package/dist/src/cli/handlers/models.js +104 -8
- package/dist/src/cli/handlers/ralph-launcher.js +2 -0
- package/dist/src/cli/handlers/refresh.js +12 -0
- package/dist/src/cli/handlers/resource-versions.js +3 -0
- package/dist/src/cli/handlers/rules.js +195 -0
- package/dist/src/cli/handlers/run.js +4 -0
- package/dist/src/cli/handlers/runtime-info.js +1 -2
- package/dist/src/cli/handlers/script-runner.js +3 -2
- package/dist/src/cli/handlers/session.js +19 -9
- package/dist/src/cli/handlers/sessions.js +137 -43
- package/dist/src/cli/handlers/setup-manifest.js +39 -44
- package/dist/src/cli/handlers/setup.js +41 -0
- package/dist/src/cli/handlers/steward.js +11 -0
- package/dist/src/cli/handlers/subcommands.js +57 -2
- package/dist/src/cli/handlers/uhp.js +3 -0
- package/dist/src/cli/handlers/use.js +294 -55
- package/dist/src/cli/handlers/utilities.js +26 -10
- package/dist/src/cli/handlers/version.js +8 -0
- package/dist/src/cli/router.js +15 -1
- package/dist/src/cli/services/deployment-verification.js +19 -9
- package/dist/src/cli/services/provider-prepare-coordinator.js +24 -0
- package/dist/src/cli/skill-usage.js +2 -3
- package/dist/src/config/aiwg-config.js +87 -37
- package/dist/src/config/cli.js +111 -5
- package/dist/src/config/decision-config.js +106 -0
- package/dist/src/config/dispatch-thresholds-runtime.d.mts +8 -0
- package/dist/src/config/gitignore.js +50 -0
- package/dist/src/config/index-graph-overrides.js +36 -0
- package/dist/src/config/project-artifacts-health.d.mts +37 -0
- package/dist/src/config/project-artifacts-runtime.d.mts +16 -0
- package/dist/src/config/user-config-dir.d.mts +9 -0
- package/dist/src/config/user-config.js +2 -1
- package/dist/src/config/user-registry.js +23 -15
- package/dist/src/config/user-state.js +7 -0
- package/dist/src/decision/ask.js +203 -43
- package/dist/src/decision/context-live-qualification.js +2 -2
- package/dist/src/decision/driver.js +13 -8
- package/dist/src/decision/ensemble/aggregate.js +23 -10
- package/dist/src/decision/ensemble-study/multifact-study.js +6 -2
- package/dist/src/decision/entry.js +2 -15
- package/dist/src/decision/graph-live-qualification.js +1 -1
- package/dist/src/decision/graph-run.js +9 -0
- package/dist/src/decision/heldout/collector.js +5 -13
- package/dist/src/decision/heldout/contract.js +30 -7
- package/dist/src/decision/jev-credentials.js +15 -1
- package/dist/src/decision/ledger.js +131 -28
- package/dist/src/decision/offload-activation.js +137 -0
- package/dist/src/decision/offload.js +550 -0
- package/dist/src/decision/qualification/load.js +4 -6
- package/dist/src/decision/savings.js +28 -0
- package/dist/src/effects/types.js +5 -1
- package/dist/src/effects/verifiers/git.js +2 -1
- package/dist/src/effects/verifiers/tracker.js +26 -14
- package/dist/src/extensions/commands/definitions.js +19 -0
- package/dist/src/extensions/project-local-remove.js +36 -1
- package/dist/src/gates/providers/loader.js +61 -17
- package/dist/src/installation/manager-command.d.mts +18 -0
- package/dist/src/installation/manager-command.mjs +14 -2
- package/dist/src/installation/manager.d.mts +49 -0
- package/dist/src/installation/manager.mjs +78 -3
- package/dist/src/issues/cli.js +4 -4
- package/dist/src/issues/local.js +24 -5
- package/dist/src/jobs/runner.js +1 -1
- package/dist/src/lint/cli.js +27 -1
- package/dist/src/lint/loader.js +2 -1
- package/dist/src/lint/reporters.js +25 -1
- package/dist/src/lint/rule-activation.js +43 -0
- package/dist/src/lint/runner.js +137 -62
- package/dist/src/mcp/adapters/codex-runtime.js +7 -1
- package/dist/src/mcp/claude-install.d.mts +4 -0
- package/dist/src/mcp/claude-install.mjs +41 -0
- package/dist/src/mcp/cli.d.mts +1 -0
- package/dist/src/mcp/cli.mjs +36 -11
- package/dist/src/mcp/config-file.d.mts +28 -0
- package/dist/src/mcp/config-layers.d.mts +36 -0
- package/dist/src/mcp/credentials.d.mts +51 -0
- package/dist/src/mcp/grok-build-config.d.mts +6 -0
- package/dist/src/mcp/grok-build-config.mjs +1 -1
- package/dist/src/mcp/helpers.mjs +3 -2
- package/dist/src/mcp/muse-native-config.d.mts +13 -0
- package/dist/src/mcp/muse-native-config.mjs +1 -0
- package/dist/src/mcp/omp-config.d.mts +4 -0
- package/dist/src/mcp/registry.js +3 -93
- package/dist/src/mcp/registry.mjs +3 -87
- package/dist/src/mcp/server-config.d.mts +3 -0
- package/dist/src/mcp/server-config.mjs +89 -0
- package/dist/src/mcp/server.mjs +37 -375
- package/dist/src/mcp/test-simple.mjs +2 -1
- package/dist/src/mcp/toml-editor.d.mts +4 -0
- package/dist/src/mcp/toml-string.d.mts +1 -0
- package/dist/src/mcp/toml-strip-mcp.d.mts +1 -0
- package/dist/src/mcp/tool-filters.d.mts +46 -0
- package/dist/src/mcp/tools/decision.mjs +12 -11
- package/dist/src/mcp/tools/discovery.mjs +62 -55
- package/dist/src/mcp/tools/orchestration.mjs +12 -2
- package/dist/src/mcp/tools/worker-invoke.mjs +1340 -23
- package/dist/src/mcp/tools/worker-shell-control.mjs +52 -21
- package/dist/src/mcp/tools/worker-stdin-runtime.mjs +3 -3
- package/dist/src/mission-protocol/codecs.js +8 -0
- package/dist/src/models/config-loader.js +7 -2
- package/dist/src/models/model-capabilities.v1.json +96 -4
- package/dist/src/models/model-discovery.js +76 -11
- package/dist/src/models/provider-policy.js +101 -7
- package/dist/src/models/qualification-digest.d.mts +2 -0
- package/dist/src/models/resolver.js +12 -10
- package/dist/src/models/worker-direct-readiness.js +65 -44
- package/dist/src/models/worker-dispatch-offer.js +3 -3
- package/dist/src/models/worker-health-readiness.js +16 -3
- package/dist/src/models/worker-installation.d.mts +39 -0
- package/dist/src/models/worker-installation.mjs +135 -28
- package/dist/src/process/bounded-child-process.d.mts +84 -0
- package/dist/src/process/bounded-child-process.mjs +43 -0
- package/dist/src/process/cli-command.d.mts +16 -0
- package/dist/src/process/signal-child.d.mts +6 -0
- package/dist/src/process/signal-child.mjs +7 -0
- package/dist/src/providers/deployed-skill-roots.js +20 -0
- package/dist/src/providers/grok-build-acp.js +3 -2
- package/dist/src/providers/grok-build-headless.js +12 -4
- package/dist/src/providers/grok-build-inspect.js +1 -0
- package/dist/src/providers/omp-agent.d.mts +8 -0
- package/dist/src/providers/omp-diagnostics.d.mts +1 -0
- package/dist/src/providers/omp-paths.d.mts +13 -0
- package/dist/src/providers/skill-ownership.d.mts +9 -0
- package/dist/src/providers/skill-ownership.mjs +88 -0
- package/dist/src/rules/activation.d.mts +21 -0
- package/dist/src/rules/activation.mjs +62 -0
- package/dist/src/rules/inline-budget.d.mts +26 -0
- package/dist/src/rules/inline-budget.mjs +66 -0
- package/dist/src/rules/inventory.js +129 -0
- package/dist/src/rules/pin-state.js +73 -0
- package/dist/src/rules/pins.d.mts +14 -0
- package/dist/src/rules/pins.mjs +112 -0
- package/dist/src/rules/rule-frontmatter.schema.json +197 -0
- package/dist/src/serve/mission-conductor.js +38 -12
- package/dist/src/serve/worker-invocation.js +30 -10
- package/dist/src/serve/worker-stdin-frame.d.mts +10 -0
- package/dist/src/sessions/adapter-registry.js +23 -19
- package/dist/src/sessions/adapters/claude.js +39 -54
- package/dist/src/sessions/adapters/codex.js +95 -67
- package/dist/src/sessions/adapters/copilot.js +4 -4
- package/dist/src/sessions/adapters/cursor.js +40 -57
- package/dist/src/sessions/adapters/deepseek-harness.js +15 -12
- package/dist/src/sessions/adapters/factory.js +31 -27
- package/dist/src/sessions/adapters/generic.js +10 -10
- package/dist/src/sessions/adapters/grok-build.js +11 -11
- package/dist/src/sessions/adapters/grokbot.js +24 -13
- package/dist/src/sessions/adapters/hermes.js +27 -29
- package/dist/src/sessions/adapters/muse-workspace-scan.js +31 -11
- package/dist/src/sessions/adapters/muse.js +150 -93
- package/dist/src/sessions/adapters/omp.js +28 -14
- package/dist/src/sessions/adapters/openclaw.js +89 -79
- package/dist/src/sessions/adapters/opencode.js +27 -24
- package/dist/src/sessions/adapters/openhuman.js +18 -60
- package/dist/src/sessions/adapters/pi.js +29 -14
- package/dist/src/sessions/adapters/warp.js +3 -3
- package/dist/src/sessions/adapters/windsurf.js +19 -48
- package/dist/src/sessions/batch-contracts.js +16 -8
- package/dist/src/sessions/batch-import.js +252 -12
- package/dist/src/sessions/contracts.js +50 -3
- package/dist/src/sessions/coverage-preflight.js +27 -2
- package/dist/src/sessions/export-plan.js +4 -3
- package/dist/src/sessions/import-exclusions.js +72 -0
- package/dist/src/sessions/import-lease.js +34 -18
- package/dist/src/sessions/import-limits.js +37 -0
- package/dist/src/sessions/importer.js +60 -38
- package/dist/src/sessions/lifecycle.js +45 -0
- package/dist/src/sessions/policy.js +145 -26
- package/dist/src/sessions/readers.js +106 -53
- package/dist/src/sessions/refresh.js +105 -0
- package/dist/src/sessions/repository.js +151 -54
- package/dist/src/sessions/workspace-discovery.js +292 -60
- package/dist/src/sessions/workspace-matching.js +107 -0
- package/dist/src/skills/importer.js +1 -0
- package/dist/src/smiths/context-pipeline/aiwg-md.js +11 -0
- package/dist/src/smiths/context-pipeline/claude-hook.js +3 -26
- package/dist/src/smiths/context-pipeline/finalization.js +6 -5
- package/dist/src/smiths/context-pipeline/generator.js +21 -4
- package/dist/src/smiths/context-pipeline/parallelism-section.js +7 -15
- package/dist/src/smiths/mcpsmith/generator.js +1 -1
- package/dist/src/smiths/toolsmith/runtime-discovery.d.mts +10 -0
- package/dist/src/smiths/toolsmith/runtime-discovery.mjs +76 -18
- package/dist/src/storage/backends/fs.js +18 -76
- package/dist/src/storage/backends/obsidian.js +1 -0
- package/dist/src/tracker/forge-http.js +2 -1
- package/dist/src/update/checker.d.mts +2 -0
- package/dist/src/update/service.d.mts +22 -0
- package/dist/src/utils/contained-filesystem.d.mts +37 -0
- package/dist/src/utils/contained-filesystem.mjs +683 -0
- package/dist/src/utils/temporary-directory.js +36 -0
- package/dist/src/writing/example-generator.js +1 -1
- package/package.json +15 -2
- package/schemas/models/model-catalog.v1.schema.json +1 -1
- package/schemas/models/provider-model-capabilities.v1.schema.json +6 -0
- package/tools/_resolve-impl.mjs +13 -15
- package/tools/agents/deploy-agents.mjs +49 -21
- package/tools/agents/providers/base.mjs +104 -134
- package/tools/agents/providers/claude.mjs +8 -24
- package/tools/agents/providers/codex.mjs +18 -11
- package/tools/agents/providers/cursor.mjs +19 -18
- package/tools/agents/providers/grok-build.mjs +3 -1
- package/tools/agents/providers/hermes.mjs +57 -47
- package/tools/agents/providers/openclaw.mjs +9 -4
- package/tools/agents/providers/pi.mjs +5 -0
- package/tools/agents/providers/provider-prepare-channel.mjs +5 -0
- package/tools/agents/providers/provider-prepare-coordinator.mjs +247 -0
- package/tools/agents/providers/provider-prepare-worker.mjs +84 -0
- package/tools/agents/providers/rule-delivery.mjs +1270 -0
- package/tools/agents/providers/warp.mjs +20 -26
- package/tools/cli/decision-offload-report.mjs +40 -0
- package/tools/cli/workspace-status.mjs +862 -0
- package/tools/plugin/package-plugins.mjs +186 -8
- package/tools/plugin/plugin-links.mjs +114 -0
- package/tools/providers/antigravity-transport.mjs +3 -1
- package/tools/providers/deepseek-harness-transport.mjs +6 -4
- package/tools/providers/omp-transport.mjs +4 -2
- package/tools/ralph-external/lib/consecutive-failure-circuit-breaker.mjs +1748 -0
- package/tools/ralph-external/lib/launcher-recovery-evidence.mjs +55 -0
- package/tools/ralph-external/lib/omp-adapter.mjs +1 -1
- package/tools/ralph-external/lib/pi-adapter.mjs +0 -4
- package/tools/ralph-external/lib/provider-adapter.mjs +59 -1
- package/tools/ralph-external/lib/recovery-probe.mjs +72 -0
- package/tools/ralph-external/lib/session-environment.mjs +28 -0
- package/tools/ralph-external/lib/systemd-client.mjs +49 -0
- package/tools/ralph-external/lib/worker-health.mjs +1 -1
- package/tools/ralph-external/lib/worker-isolation.mjs +717 -0
- package/tools/ralph-external/lib/worker-overflow.mjs +50 -0
- package/tools/ralph-external/process-monitor.mjs +1017 -0
- package/tools/ralph-external/session-launcher.mjs +5057 -0
- package/tools/scripts/resource-preflight.mjs +541 -0
- package/tools/skills/deploy-skills-codex.mjs +2 -1
- package/vscode-extension/schemas/aiwg.config.v1.json +1194 -231
package/dist/src/issues/local.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { constants } from 'fs';
|
|
2
|
-
import { mkdir, open, readFile, readdir, rename, unlink, writeFile } from 'fs/promises';
|
|
2
|
+
import { mkdir, open, readFile, readdir, rename, stat, unlink, writeFile } from 'fs/promises';
|
|
3
|
+
import { createHash } from 'node:crypto';
|
|
3
4
|
import { basename, dirname, join, relative, resolve, sep } from 'path';
|
|
4
5
|
import { hostname } from 'os';
|
|
5
6
|
import { parse as parseYaml, stringify as stringifyYaml } from 'yaml';
|
|
@@ -328,15 +329,32 @@ export class LocalIssueProviderCore {
|
|
|
328
329
|
const idx = events.findIndex((event) => event.event_id === since);
|
|
329
330
|
return idx >= 0 ? events.slice(idx + 1) : events;
|
|
330
331
|
}
|
|
331
|
-
async rebuildIssueIndex() {
|
|
332
|
+
async rebuildIssueIndex(options = {}) {
|
|
332
333
|
await this.ensureLayout();
|
|
333
334
|
return this.lockManager.withIndexLock('rebuild issue index', async () => {
|
|
334
335
|
const itemDir = this.path('items');
|
|
335
|
-
const entries = await readdir(itemDir, { withFileTypes: true })
|
|
336
|
+
const entries = (await readdir(itemDir, { withFileTypes: true }))
|
|
337
|
+
.filter(entry => entry.isFile() && entry.name.endsWith('.md'))
|
|
338
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
339
|
+
const inputs = await Promise.all(entries.map(async (entry) => {
|
|
340
|
+
const metadata = await stat(join(itemDir, entry.name));
|
|
341
|
+
return [entry.name, metadata.mtimeMs, metadata.ctimeMs, metadata.size];
|
|
342
|
+
}));
|
|
343
|
+
const inputFingerprint = createHash('sha256').update(JSON.stringify([1, inputs])).digest('hex');
|
|
344
|
+
if (!options.force) {
|
|
345
|
+
try {
|
|
346
|
+
const cached = JSON.parse(await readFile(this.path('index/issues.index.json'), 'utf-8'));
|
|
347
|
+
if (cached?.provider === 'local' && Array.isArray(cached.issues) && cached.inputFingerprint === inputFingerprint) {
|
|
348
|
+
return cached;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
catch (error) {
|
|
352
|
+
if (!(error instanceof SyntaxError) && !(isNodeError(error) && error.code === 'ENOENT'))
|
|
353
|
+
throw error;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
336
356
|
const issues = [];
|
|
337
357
|
for (const entry of entries) {
|
|
338
|
-
if (!entry.isFile() || !entry.name.endsWith('.md'))
|
|
339
|
-
continue;
|
|
340
358
|
const record = parseIssueMarkdown(await readFile(join(itemDir, entry.name), 'utf-8'));
|
|
341
359
|
issues.push({
|
|
342
360
|
id: record.fields.id,
|
|
@@ -355,6 +373,7 @@ export class LocalIssueProviderCore {
|
|
|
355
373
|
const index = {
|
|
356
374
|
provider: 'local',
|
|
357
375
|
rebuilt_at: new Date().toISOString(),
|
|
376
|
+
inputFingerprint,
|
|
358
377
|
issues,
|
|
359
378
|
};
|
|
360
379
|
await atomicWrite(this.path('index/issues.index.json'), `${JSON.stringify(index, null, 2)}\n`);
|
package/dist/src/jobs/runner.js
CHANGED
|
@@ -184,7 +184,7 @@ async function acquireLock(stateRoot, flow) {
|
|
|
184
184
|
const lock = JSON.parse(await fs.readFile(file, 'utf8'));
|
|
185
185
|
const tooOld = !lock.startedAt || Date.now() - Date.parse(lock.startedAt) > (flow.spec.workItem.claimTtlSeconds ?? 900) * 1000;
|
|
186
186
|
let alive = false;
|
|
187
|
-
if (
|
|
187
|
+
if (Number.isSafeInteger(lock.pid) && (lock.pid ?? 0) > 0) {
|
|
188
188
|
try {
|
|
189
189
|
process.kill(lock.pid, 0);
|
|
190
190
|
alive = true;
|
package/dist/src/lint/cli.js
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
* @issue #810
|
|
14
14
|
*/
|
|
15
15
|
import fs from 'fs';
|
|
16
|
+
import { readAiwgConfigCandidate } from '../config/aiwg-config.js';
|
|
16
17
|
import path from 'path';
|
|
17
18
|
import { fileURLToPath } from 'url';
|
|
18
19
|
import { discoverRulesets } from './loader.js';
|
|
@@ -71,9 +72,15 @@ function parseArgs(args) {
|
|
|
71
72
|
else if (arg === '--format' && i + 1 < args.length) {
|
|
72
73
|
options.format = args[++i];
|
|
73
74
|
}
|
|
75
|
+
else if (arg === '--startup-plan' && i + 1 < args.length) {
|
|
76
|
+
options.startupPlan = args[++i];
|
|
77
|
+
}
|
|
74
78
|
else if (arg === '--ci') {
|
|
75
79
|
options.ci = true;
|
|
76
80
|
}
|
|
81
|
+
else if (arg === '--allow-empty') {
|
|
82
|
+
options.allowEmpty = true;
|
|
83
|
+
}
|
|
77
84
|
else if (arg === '--fail-on' && i + 1 < args.length) {
|
|
78
85
|
options.failOn = args[++i];
|
|
79
86
|
}
|
|
@@ -102,6 +109,14 @@ function parseArgs(args) {
|
|
|
102
109
|
export async function main(args) {
|
|
103
110
|
const options = parseArgs(args);
|
|
104
111
|
const cwd = process.cwd();
|
|
112
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
113
|
+
console.log('Usage: aiwg lint <target> [--ruleset <name>] [--format full|summary|json] [--ci] [--allow-empty] [--fail-on error|warn|info] [--no-gitignore]');
|
|
114
|
+
console.log(' --ci Fail on lint violations, zero files checked or zero rules applied');
|
|
115
|
+
console.log(' --allow-empty Allow zero files or zero applied rules under --ci');
|
|
116
|
+
console.log(' --startup-plan <json> Guard a complete projected startup set { provider, files: [{ path, bytes | content }] }');
|
|
117
|
+
console.log(' --list-rulesets | --list-rules <ruleset>');
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
105
120
|
// Discover available rulesets. `cwd` is where the user runs the lint (used
|
|
106
121
|
// to find deployed `.aiwg/frameworks/*/lint/` overlays); `AIWG_ROOT` is the
|
|
107
122
|
// AIWG install (used to find shipped `agentic/code/frameworks/*/lint/`).
|
|
@@ -126,7 +141,7 @@ export async function main(args) {
|
|
|
126
141
|
}
|
|
127
142
|
// Require a target for actual linting
|
|
128
143
|
if (!options.target) {
|
|
129
|
-
console.error('Usage: aiwg lint <target> [--ruleset <name>] [--format full|summary|json] [--ci] [--fail-on error|warn|info] [--no-gitignore]');
|
|
144
|
+
console.error('Usage: aiwg lint <target> [--ruleset <name>] [--format full|summary|json] [--ci] [--allow-empty] [--fail-on error|warn|info] [--no-gitignore]');
|
|
130
145
|
console.error(' aiwg lint --list-rulesets');
|
|
131
146
|
console.error(' aiwg lint --list-rules <ruleset>');
|
|
132
147
|
process.exitCode = 1;
|
|
@@ -159,9 +174,20 @@ export async function main(args) {
|
|
|
159
174
|
}
|
|
160
175
|
return;
|
|
161
176
|
}
|
|
177
|
+
let startupBudget;
|
|
178
|
+
if (options.startupPlan) {
|
|
179
|
+
const plan = JSON.parse(fs.readFileSync(path.resolve(cwd, options.startupPlan), 'utf8'));
|
|
180
|
+
if (!plan || typeof plan.provider !== 'string' || !Array.isArray(plan.files)) {
|
|
181
|
+
throw new Error('Startup plan must contain provider and files (the complete assembled startup set).');
|
|
182
|
+
}
|
|
183
|
+
startupBudget = { provider: plan.provider, files: plan.files, config: await readAiwgConfigCandidate(cwd) ?? {} };
|
|
184
|
+
}
|
|
162
185
|
// Run lint
|
|
163
186
|
const result = await runLint(targetDir, selectedRulesets, {
|
|
187
|
+
startupBudget,
|
|
164
188
|
recursive: options.recursive,
|
|
189
|
+
ci: options.ci,
|
|
190
|
+
allowEmpty: options.allowEmpty,
|
|
165
191
|
failOn: options.failOn,
|
|
166
192
|
respectGitignore: options.respectGitignore ?? true,
|
|
167
193
|
});
|
package/dist/src/lint/loader.js
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* @issue #810
|
|
9
9
|
*/
|
|
10
10
|
import fs from 'fs';
|
|
11
|
+
import { ruleActivationRuleset } from './rule-activation.js';
|
|
11
12
|
import fsp from 'fs/promises';
|
|
12
13
|
import path from 'path';
|
|
13
14
|
import { projectAiwgPath } from '../config/project-artifacts.js';
|
|
@@ -178,7 +179,7 @@ export async function loadRuleset(lintDir, frameworkId) {
|
|
|
178
179
|
* 2. Deployed frameworks: .aiwg/frameworks/{id}/lint/
|
|
179
180
|
*/
|
|
180
181
|
export async function discoverRulesets(cwd, frameworkRoot) {
|
|
181
|
-
const rulesets = [];
|
|
182
|
+
const rulesets = [ruleActivationRuleset];
|
|
182
183
|
// Check framework source directories
|
|
183
184
|
const frameworksDir = path.join(frameworkRoot, 'agentic', 'code', 'frameworks');
|
|
184
185
|
if (fs.existsSync(frameworksDir)) {
|
|
@@ -31,14 +31,33 @@ function formatDiagnostic(d) {
|
|
|
31
31
|
/**
|
|
32
32
|
* Format lint results in full detail
|
|
33
33
|
*/
|
|
34
|
+
function formatBudget(result) {
|
|
35
|
+
const report = result.inlineBudget;
|
|
36
|
+
if (!report)
|
|
37
|
+
return [];
|
|
38
|
+
return [
|
|
39
|
+
`Startup budget (${report.provider}): limit ${report.limit} tokens${report.disabled ? ' (guard disabled)' : ''}; selected ${report.selected_tokens} estimated tokens (${report.selected_bytes} bytes; ${report.estimate})`,
|
|
40
|
+
...report.contributors.map(file => ` ${file.path}: ${file.bytes} bytes (${file.estimated_tokens} estimated tokens)`),
|
|
41
|
+
];
|
|
42
|
+
}
|
|
34
43
|
function formatFull(result) {
|
|
35
44
|
const lines = [];
|
|
36
45
|
lines.push(`${BOLD}AIWG Lint${RESET}`);
|
|
37
46
|
lines.push(`Target: ${result.target}`);
|
|
38
47
|
lines.push(`Rulesets: ${result.rulesets.join(', ')}`);
|
|
39
48
|
lines.push(`Files checked: ${result.summary.filesChecked}`);
|
|
49
|
+
lines.push(...formatBudget(result));
|
|
40
50
|
lines.push(`Rules applied: ${result.summary.rulesApplied} of ${result.summary.rulesSelected}`);
|
|
41
51
|
lines.push('');
|
|
52
|
+
if (result.summary.coverageMessage)
|
|
53
|
+
lines.push(`! ${result.summary.coverageMessage}`);
|
|
54
|
+
if (result.ruleCoverage) {
|
|
55
|
+
lines.push('Rule coverage (files by rule):');
|
|
56
|
+
for (const [ruleId, files] of Object.entries(result.ruleCoverage)) {
|
|
57
|
+
lines.push(` ${ruleId}: ${files.length ? files.join(', ') : '(no files)'}`);
|
|
58
|
+
}
|
|
59
|
+
lines.push('');
|
|
60
|
+
}
|
|
42
61
|
// A target that excludes every rule produces the same clean output as a
|
|
43
62
|
// target that passes them, which is how 34 findings stayed invisible across a
|
|
44
63
|
// long session (#2555). Say so before the verdict.
|
|
@@ -92,9 +111,14 @@ function formatFull(result) {
|
|
|
92
111
|
* Format lint results as a summary (counts only)
|
|
93
112
|
*/
|
|
94
113
|
function formatSummary(result) {
|
|
95
|
-
const lines = [];
|
|
114
|
+
const lines = [...formatBudget(result)];
|
|
96
115
|
lines.push(`Lint: ${result.summary.filesChecked} files, ${result.summary.errors} errors, ${result.summary.warnings} warnings, ${result.summary.infos} info`);
|
|
97
116
|
lines.push(`Rules applied: ${result.summary.rulesApplied} of ${result.summary.rulesSelected}`);
|
|
117
|
+
if (result.summary.coverageMessage)
|
|
118
|
+
lines.push(`! ${result.summary.coverageMessage}`);
|
|
119
|
+
if (result.summary.inapplicableRules.length > 0) {
|
|
120
|
+
lines.push(`Inapplicable rules: ${result.summary.inapplicableRules.join(', ')}`);
|
|
121
|
+
}
|
|
98
122
|
if (result.summary.rulesSelected > 0 && result.summary.rulesApplied === 0) {
|
|
99
123
|
lines.push(`! No rule applies to this target — this is not a clean result (#2555).`);
|
|
100
124
|
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { loadLocalCapabilityEntries, resolveRuleBindingTarget } from '../artifacts/query-engine.js';
|
|
2
|
+
import { parseRuleFrontmatter, RULE_BINDING_TYPES } from '../rules/activation.mjs';
|
|
3
|
+
export const ruleActivationRuleset = {
|
|
4
|
+
id: 'rule-activation', name: 'Rule activation', framework: 'core', version: '1.0.0',
|
|
5
|
+
description: 'Validate rule and behavior activation contracts and exact indexed bindings',
|
|
6
|
+
rules: [{
|
|
7
|
+
id: 'rules/activation', name: 'Rule activation contract', severity: 'error',
|
|
8
|
+
description: 'Validate activation, summaries, paths, bindings, and migration fallback',
|
|
9
|
+
appliesTo: { glob: '**/{rules/**/*.md,behaviors/**/BEHAVIOR.md}' },
|
|
10
|
+
checks: [{ type: 'rule-activation' }],
|
|
11
|
+
}],
|
|
12
|
+
};
|
|
13
|
+
export function lintRuleActivation(content, file, projectRoot, entries) {
|
|
14
|
+
// Generated structural indexes are not authored rules.
|
|
15
|
+
if (/^(?:RULES-INDEX|RULES-ONDEMAND)\.md$/.test(file.split('/').pop()))
|
|
16
|
+
return [];
|
|
17
|
+
const result = parseRuleFrontmatter(content);
|
|
18
|
+
const diagnostics = result.diagnostics.map(item => ({
|
|
19
|
+
ruleId: 'rules/activation', ruleName: 'Rule activation contract',
|
|
20
|
+
file, severity: item.severity, message: item.message,
|
|
21
|
+
}));
|
|
22
|
+
if (!result.rule)
|
|
23
|
+
return diagnostics;
|
|
24
|
+
const bindings = result.rule.bound_to;
|
|
25
|
+
if (!Object.values(bindings).some(list => list.length > 0))
|
|
26
|
+
return diagnostics;
|
|
27
|
+
// Reuse existing artifact index ownership and show precedence. No title guessing,
|
|
28
|
+
// corpus directory resolver, or extra binding registry.
|
|
29
|
+
const indexed = entries ?? loadLocalCapabilityEntries(projectRoot);
|
|
30
|
+
for (const key of Object.keys(RULE_BINDING_TYPES)) {
|
|
31
|
+
for (const id of bindings[key]) {
|
|
32
|
+
try {
|
|
33
|
+
resolveRuleBindingTarget(indexed, RULE_BINDING_TYPES[key], id);
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
diagnostics.push({ ruleId: 'rules/activation', ruleName: 'Rule activation contract',
|
|
37
|
+
file, severity: 'error', message: error instanceof Error ? error.message : String(error) });
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return diagnostics;
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=rule-activation.js.map
|
package/dist/src/lint/runner.js
CHANGED
|
@@ -7,27 +7,54 @@
|
|
|
7
7
|
* @issue #810
|
|
8
8
|
*/
|
|
9
9
|
import fs from 'fs';
|
|
10
|
-
import {
|
|
10
|
+
import { lintRuleActivation } from './rule-activation.js';
|
|
11
|
+
import { guardRulesInlineBudget } from '../rules/inline-budget.mjs';
|
|
12
|
+
import { dropGitignored } from '../config/gitignore.js';
|
|
11
13
|
import fsp from 'fs/promises';
|
|
12
14
|
import path from 'path';
|
|
13
15
|
import { glob } from 'glob';
|
|
14
16
|
import { minimatch } from 'minimatch';
|
|
17
|
+
import { parse } from 'yaml';
|
|
15
18
|
/**
|
|
16
19
|
* Parse YAML-style frontmatter from a markdown file
|
|
17
20
|
* Returns key-value pairs from the --- delimited block
|
|
18
21
|
*/
|
|
19
22
|
function parseFrontmatter(content) {
|
|
20
|
-
const match = content.match(/^---\n([\s\S]*?)\n
|
|
23
|
+
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/);
|
|
21
24
|
if (!match)
|
|
22
25
|
return {};
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
try {
|
|
27
|
+
const parsed = parse(match[1]);
|
|
28
|
+
return parsed !== null && typeof parsed === 'object' && !Array.isArray(parsed)
|
|
29
|
+
? parsed
|
|
30
|
+
: {};
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
// Invalid YAML cannot supply required metadata.
|
|
34
|
+
return {};
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function isPresent(value) {
|
|
38
|
+
if (value == null)
|
|
39
|
+
return false;
|
|
40
|
+
if (typeof value === 'string')
|
|
41
|
+
return value.trim().length > 0;
|
|
42
|
+
if (Array.isArray(value))
|
|
43
|
+
return value.length > 0;
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
/** Resolve project-relative metadata from the nearest repository/workspace marker. */
|
|
47
|
+
function findProjectRoot(targetDir) {
|
|
48
|
+
let dir = path.resolve(targetDir);
|
|
49
|
+
while (true) {
|
|
50
|
+
if (fs.existsSync(path.join(dir, '.git')) || fs.existsSync(path.join(dir, '.aiwg'))) {
|
|
51
|
+
return dir;
|
|
28
52
|
}
|
|
53
|
+
const parent = path.dirname(dir);
|
|
54
|
+
if (parent === dir)
|
|
55
|
+
return path.resolve(targetDir);
|
|
56
|
+
dir = parent;
|
|
29
57
|
}
|
|
30
|
-
return result;
|
|
31
58
|
}
|
|
32
59
|
const DEFAULT_REFERENCE_PATTERN = '\\bREF-\\d{3,}\\b';
|
|
33
60
|
/**
|
|
@@ -215,8 +242,9 @@ function buildReferenceIndex(allFiles) {
|
|
|
215
242
|
* Preserve support for references stored outside the lint target when a rule
|
|
216
243
|
* explicitly supplies basePath. Accept both exact and slugged filenames.
|
|
217
244
|
*/
|
|
218
|
-
function resolvesFromBasePath(refId, targetDir, basePath) {
|
|
245
|
+
function resolvesFromBasePath(refId, targetDir, basePath, projectRoot) {
|
|
219
246
|
const candidateDirs = new Set([
|
|
247
|
+
path.resolve(projectRoot, basePath),
|
|
220
248
|
path.resolve(targetDir, basePath),
|
|
221
249
|
path.resolve(process.cwd(), basePath),
|
|
222
250
|
]);
|
|
@@ -237,14 +265,14 @@ function resolvesFromBasePath(refId, targetDir, basePath) {
|
|
|
237
265
|
/**
|
|
238
266
|
* Run a single check against a file's content and frontmatter
|
|
239
267
|
*/
|
|
240
|
-
function runCheck(check, content, frontmatter, filePath, targetDir, allFiles, referenceIndex) {
|
|
268
|
+
function runCheck(check, content, frontmatter, filePath, targetDir, allFiles, referenceIndex, projectRoot) {
|
|
241
269
|
const diagnostics = [];
|
|
242
270
|
switch (check.type) {
|
|
243
271
|
case 'frontmatter-required': {
|
|
244
272
|
if (!check.fields)
|
|
245
273
|
break;
|
|
246
274
|
for (const field of check.fields) {
|
|
247
|
-
if (!frontmatter[field]
|
|
275
|
+
if (!isPresent(frontmatter[field])) {
|
|
248
276
|
diagnostics.push({
|
|
249
277
|
ruleId: '',
|
|
250
278
|
ruleName: '',
|
|
@@ -261,7 +289,7 @@ function runCheck(check, content, frontmatter, filePath, targetDir, allFiles, re
|
|
|
261
289
|
if (!check.field || !check.pattern)
|
|
262
290
|
break;
|
|
263
291
|
const value = frontmatter[check.field];
|
|
264
|
-
if (value && !new RegExp(check.pattern).test(value)) {
|
|
292
|
+
if (isPresent(value) && typeof value !== 'object' && !new RegExp(check.pattern).test(String(value))) {
|
|
265
293
|
diagnostics.push({
|
|
266
294
|
ruleId: '',
|
|
267
295
|
ruleName: '',
|
|
@@ -282,7 +310,9 @@ function runCheck(check, content, frontmatter, filePath, targetDir, allFiles, re
|
|
|
282
310
|
let match;
|
|
283
311
|
while ((match = regex.exec(lines[i])) !== null) {
|
|
284
312
|
const refId = match[0];
|
|
285
|
-
const
|
|
313
|
+
const basePaths = check.basePath
|
|
314
|
+
? [check.basePath]
|
|
315
|
+
: ['.aiwg/research/findings', 'findings', 'documentation/references', '.'];
|
|
286
316
|
// A corpus documents its deliberate gaps ("REF-2464 remains
|
|
287
317
|
// unallocated after deduplication"). Reading that sentence as a
|
|
288
318
|
// dangling reference left only two ways to silence an error: delete
|
|
@@ -290,7 +320,7 @@ function runCheck(check, content, frontmatter, filePath, targetDir, allFiles, re
|
|
|
290
320
|
// absent (#2555).
|
|
291
321
|
if (absence.some((re) => re.test(lines[i])))
|
|
292
322
|
continue;
|
|
293
|
-
if (!referenceIndex.has(refId) && !resolvesFromBasePath(refId, targetDir, basePath)) {
|
|
323
|
+
if (!referenceIndex.has(refId) && !basePaths.some(basePath => resolvesFromBasePath(refId, targetDir, basePath, projectRoot))) {
|
|
294
324
|
diagnostics.push({
|
|
295
325
|
ruleId: '',
|
|
296
326
|
ruleName: '',
|
|
@@ -376,9 +406,12 @@ function runCheck(check, content, frontmatter, filePath, targetDir, allFiles, re
|
|
|
376
406
|
if (!check.field)
|
|
377
407
|
break;
|
|
378
408
|
const refPath = frontmatter[check.field];
|
|
379
|
-
if (refPath) {
|
|
380
|
-
|
|
381
|
-
|
|
409
|
+
if (isPresent(refPath)) {
|
|
410
|
+
// Project-root-relative is canonical (#3145); target-relative paths
|
|
411
|
+
// written for the legacy layout keep resolving.
|
|
412
|
+
const found = typeof refPath === 'string' &&
|
|
413
|
+
[projectRoot, targetDir].some(base => fs.existsSync(path.resolve(base, refPath)));
|
|
414
|
+
if (!found) {
|
|
382
415
|
diagnostics.push({
|
|
383
416
|
ruleId: '',
|
|
384
417
|
ruleName: '',
|
|
@@ -398,7 +431,8 @@ function runCheck(check, content, frontmatter, filePath, targetDir, allFiles, re
|
|
|
398
431
|
if (!check.pattern)
|
|
399
432
|
break;
|
|
400
433
|
const basename = path.basename(filePath, '.md');
|
|
401
|
-
|
|
434
|
+
const match = basename.match(new RegExp(check.pattern));
|
|
435
|
+
if (!match) {
|
|
402
436
|
diagnostics.push({
|
|
403
437
|
ruleId: '',
|
|
404
438
|
ruleName: '',
|
|
@@ -408,6 +442,20 @@ function runCheck(check, content, frontmatter, filePath, targetDir, allFiles, re
|
|
|
408
442
|
fix: `Rename to match the pattern ${check.pattern}`,
|
|
409
443
|
});
|
|
410
444
|
}
|
|
445
|
+
else if (check.field && isPresent(frontmatter[check.field])) {
|
|
446
|
+
const identifier = match[1] ?? match[0];
|
|
447
|
+
const value = frontmatter[check.field];
|
|
448
|
+
if (value !== identifier) {
|
|
449
|
+
diagnostics.push({
|
|
450
|
+
ruleId: '',
|
|
451
|
+
ruleName: '',
|
|
452
|
+
severity: 'warn',
|
|
453
|
+
file: filePath,
|
|
454
|
+
message: `File identifier '${identifier}' does not match frontmatter field '${check.field}': ${String(value)}`,
|
|
455
|
+
fix: `Make the filename identifier and '${check.field}' field agree`,
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
}
|
|
411
459
|
break;
|
|
412
460
|
}
|
|
413
461
|
case 'cross-ref-bidirectional': {
|
|
@@ -473,15 +521,13 @@ export function ruleAppliesToFile(ruleGlob, file, targetDir) {
|
|
|
473
521
|
/**
|
|
474
522
|
* Run a single rule against all matching files in the target
|
|
475
523
|
*/
|
|
476
|
-
async function runRule(rule, targetDir, allFiles, referenceIndex) {
|
|
524
|
+
async function runRule(rule, targetDir, allFiles, referenceIndex, projectRoot, artifactEntries) {
|
|
477
525
|
const diagnostics = [];
|
|
478
|
-
RULE_APPLIED.delete(rule.id);
|
|
479
526
|
// Filter files matching the rule's glob
|
|
480
527
|
const ruleGlob = rule.appliesTo.glob;
|
|
481
528
|
const matchingFiles = allFiles.filter(f => ruleAppliesToFile(ruleGlob, f, targetDir));
|
|
482
529
|
if (matchingFiles.length === 0)
|
|
483
|
-
return diagnostics;
|
|
484
|
-
RULE_APPLIED.add(rule.id);
|
|
530
|
+
return { diagnostics, files: matchingFiles };
|
|
485
531
|
// Handle id-unique check at the ruleset level
|
|
486
532
|
const uniqueCheck = rule.checks.find(c => c.type === 'id-unique');
|
|
487
533
|
if (uniqueCheck) {
|
|
@@ -516,7 +562,11 @@ async function runRule(rule, targetDir, allFiles, referenceIndex) {
|
|
|
516
562
|
for (const check of rule.checks) {
|
|
517
563
|
if (check.type === 'id-unique')
|
|
518
564
|
continue; // Already handled
|
|
519
|
-
|
|
565
|
+
if (check.type === 'rule-activation') {
|
|
566
|
+
diagnostics.push(...lintRuleActivation(content, file, projectRoot, artifactEntries));
|
|
567
|
+
continue;
|
|
568
|
+
}
|
|
569
|
+
const checkDiagnostics = runCheck(check, content, frontmatter, file, targetDir, allFiles, referenceIndex, projectRoot);
|
|
520
570
|
for (const d of checkDiagnostics) {
|
|
521
571
|
d.ruleId = rule.id;
|
|
522
572
|
d.ruleName = rule.name;
|
|
@@ -525,10 +575,8 @@ async function runRule(rule, targetDir, allFiles, referenceIndex) {
|
|
|
525
575
|
}
|
|
526
576
|
}
|
|
527
577
|
}
|
|
528
|
-
return diagnostics;
|
|
578
|
+
return { diagnostics, files: matchingFiles };
|
|
529
579
|
}
|
|
530
|
-
/** Rule ids that selected at least one file in the current run (#2555). */
|
|
531
|
-
const RULE_APPLIED = new Set();
|
|
532
580
|
/**
|
|
533
581
|
* Collect all files under a target directory
|
|
534
582
|
*/
|
|
@@ -547,37 +595,6 @@ async function collectFiles(targetDir, recursive, respectGitignore = true) {
|
|
|
547
595
|
return [];
|
|
548
596
|
}
|
|
549
597
|
}
|
|
550
|
-
/**
|
|
551
|
-
* Drop files git ignores (#2555).
|
|
552
|
-
*
|
|
553
|
-
* Generated trees are regenerated, not authored: linting `indices/` reported an
|
|
554
|
-
* error in text that documents a deliberate gap. `git check-ignore` is asked
|
|
555
|
-
* once for the whole set so the semantics are git's own (negations, nested
|
|
556
|
-
* `.gitignore`, `core.excludesFile`) rather than a re-implementation. Outside a
|
|
557
|
-
* repository, or without git, every file is kept.
|
|
558
|
-
*/
|
|
559
|
-
function dropGitignored(targetDir, files) {
|
|
560
|
-
if (files.length === 0)
|
|
561
|
-
return files;
|
|
562
|
-
try {
|
|
563
|
-
const result = boundedSpawnSync('git', ['check-ignore', '--stdin'], 'quick', {
|
|
564
|
-
cwd: targetDir,
|
|
565
|
-
input: `${files.join('\n')}\n`,
|
|
566
|
-
encoding: 'utf8',
|
|
567
|
-
});
|
|
568
|
-
// 0 = some paths ignored, 1 = none ignored, anything else = not a repo or
|
|
569
|
-
// git unavailable, in which case nothing is filtered.
|
|
570
|
-
if (result.error || (result.status !== 0 && result.status !== 1))
|
|
571
|
-
return files;
|
|
572
|
-
const ignored = new Set((result.stdout ?? '').split('\n').map((line) => line.trim()).filter(Boolean));
|
|
573
|
-
if (ignored.size === 0)
|
|
574
|
-
return files;
|
|
575
|
-
return files.filter((file) => !ignored.has(file));
|
|
576
|
-
}
|
|
577
|
-
catch {
|
|
578
|
-
return files;
|
|
579
|
-
}
|
|
580
|
-
}
|
|
581
598
|
/**
|
|
582
599
|
* Auto-detect which rulesets apply to a target path
|
|
583
600
|
*
|
|
@@ -602,27 +619,74 @@ function autoDetectRulesets(target, available) {
|
|
|
602
619
|
}
|
|
603
620
|
}
|
|
604
621
|
// If no auto-detection, return all available
|
|
605
|
-
return matches.length > 0
|
|
622
|
+
return matches.length > 0
|
|
623
|
+
? available.filter(ruleset => ruleset.id === 'rule-activation' || matches.includes(ruleset))
|
|
624
|
+
: available;
|
|
606
625
|
}
|
|
607
626
|
/**
|
|
608
627
|
* Run lint on a target path with specified or auto-detected rulesets
|
|
609
628
|
*/
|
|
610
629
|
export async function runLint(targetDir, rulesets, options = {}) {
|
|
611
630
|
const recursive = options.recursive ?? true;
|
|
612
|
-
const
|
|
631
|
+
const targetStat = await fsp.stat(targetDir).catch(() => null);
|
|
632
|
+
const scanDir = targetStat?.isFile() ? path.dirname(targetDir) : targetDir;
|
|
633
|
+
const projectRoot = findProjectRoot(scanDir);
|
|
634
|
+
const respectGitignore = options.respectGitignore ?? true;
|
|
635
|
+
let allFiles;
|
|
636
|
+
let targetMessage = '';
|
|
637
|
+
if (!targetStat) {
|
|
638
|
+
allFiles = [];
|
|
639
|
+
targetMessage = `Target does not exist or is inaccessible: ${targetDir}.`;
|
|
640
|
+
}
|
|
641
|
+
else if (targetStat.isFile()) {
|
|
642
|
+
const file = path.basename(targetDir);
|
|
643
|
+
if (!/\.(md|yaml|yml|json)$/.test(file)) {
|
|
644
|
+
allFiles = [];
|
|
645
|
+
targetMessage = `Unsupported file type: ${file}. Expected .md, .yaml, .yml or .json.`;
|
|
646
|
+
}
|
|
647
|
+
else {
|
|
648
|
+
allFiles = respectGitignore ? dropGitignored(scanDir, [file]) : [file];
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
else {
|
|
652
|
+
allFiles = await collectFiles(scanDir, recursive, respectGitignore);
|
|
653
|
+
}
|
|
613
654
|
const referenceIndex = buildReferenceIndex(allFiles);
|
|
614
655
|
const allDiagnostics = [];
|
|
615
656
|
const inapplicableRules = [];
|
|
657
|
+
const ruleCoverage = {};
|
|
616
658
|
let rulesSelected = 0;
|
|
617
659
|
for (const ruleset of rulesets) {
|
|
618
660
|
for (const rule of ruleset.rules) {
|
|
619
661
|
rulesSelected += 1;
|
|
620
|
-
const diagnostics = await runRule(rule,
|
|
621
|
-
|
|
662
|
+
const { diagnostics, files } = await runRule(rule, scanDir, allFiles, referenceIndex, projectRoot, options.artifactEntries);
|
|
663
|
+
ruleCoverage[rule.id] = files;
|
|
664
|
+
if (files.length === 0)
|
|
622
665
|
inapplicableRules.push(rule.id);
|
|
623
666
|
allDiagnostics.push(...diagnostics);
|
|
624
667
|
}
|
|
625
668
|
}
|
|
669
|
+
let budget;
|
|
670
|
+
if (options.startupBudget) {
|
|
671
|
+
try {
|
|
672
|
+
budget = guardRulesInlineBudget({
|
|
673
|
+
...options.startupBudget, reportOnly: options.ci ? false : (options.startupBudget.reportOnly ?? true),
|
|
674
|
+
});
|
|
675
|
+
}
|
|
676
|
+
catch (error) {
|
|
677
|
+
allDiagnostics.push({
|
|
678
|
+
ruleId: 'rules/inline-budget', ruleName: 'Rules inline budget', file: '(assembled startup)',
|
|
679
|
+
severity: 'error', message: error instanceof Error ? error.message : String(error),
|
|
680
|
+
});
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
if (budget) {
|
|
684
|
+
for (const item of budget.diagnostics)
|
|
685
|
+
allDiagnostics.push({
|
|
686
|
+
ruleId: 'rules/inline-budget', ruleName: 'Rules inline budget', file: '(assembled startup)',
|
|
687
|
+
severity: item.severity, message: item.message,
|
|
688
|
+
});
|
|
689
|
+
}
|
|
626
690
|
const errors = allDiagnostics.filter(d => d.severity === 'error').length;
|
|
627
691
|
const warnings = allDiagnostics.filter(d => d.severity === 'warn').length;
|
|
628
692
|
const infos = allDiagnostics.filter(d => d.severity === 'info').length;
|
|
@@ -634,10 +698,20 @@ export async function runLint(targetDir, rulesets, options = {}) {
|
|
|
634
698
|
passed = false;
|
|
635
699
|
if (failOn === 'info' && allDiagnostics.length > 0)
|
|
636
700
|
passed = false;
|
|
701
|
+
const rulesApplied = rulesSelected - inapplicableRules.length;
|
|
702
|
+
const coverageMessage = allFiles.length === 0
|
|
703
|
+
? `No files checked. ${targetMessage || 'The target is empty, contains no supported files, or all files are ignored.'}`
|
|
704
|
+
: rulesApplied === 0
|
|
705
|
+
? 'No rules applied. Check the target path and selected ruleset globs.'
|
|
706
|
+
: undefined;
|
|
707
|
+
if (options.ci && !options.allowEmpty && coverageMessage)
|
|
708
|
+
passed = false;
|
|
637
709
|
return {
|
|
638
710
|
target: targetDir,
|
|
639
711
|
rulesets: rulesets.map(r => r.id),
|
|
640
712
|
diagnostics: allDiagnostics,
|
|
713
|
+
...(budget ? { inlineBudget: budget.report } : {}),
|
|
714
|
+
ruleCoverage,
|
|
641
715
|
summary: {
|
|
642
716
|
filesChecked: allFiles.length,
|
|
643
717
|
errors,
|
|
@@ -647,8 +721,9 @@ export async function runLint(targetDir, rulesets, options = {}) {
|
|
|
647
721
|
// A run where no rule selected a file must never read the same as a clean
|
|
648
722
|
// run (#2555). Reporters surface this; callers can gate on it.
|
|
649
723
|
rulesSelected,
|
|
650
|
-
rulesApplied
|
|
724
|
+
rulesApplied,
|
|
651
725
|
inapplicableRules,
|
|
726
|
+
coverageMessage,
|
|
652
727
|
},
|
|
653
728
|
timestamp: new Date().toISOString(),
|
|
654
729
|
};
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
*
|
|
15
15
|
* @implements #892
|
|
16
16
|
*/
|
|
17
|
+
import { signalChild } from '../../process/signal-child.mjs';
|
|
17
18
|
import { readFile, mkdir, symlink, access, readdir, chmod, lstat, open } from 'fs/promises';
|
|
18
19
|
import { join } from 'path';
|
|
19
20
|
import { homedir } from 'os';
|
|
@@ -27,6 +28,7 @@ import { assertCredentialPolicy, resolveCredentialPolicy } from '../credentials.
|
|
|
27
28
|
import { planToolFilters } from '../tool-filters.mjs';
|
|
28
29
|
import { assertConfigDestination, writeConfigAtomic } from '../config-file.mjs';
|
|
29
30
|
import { stripMcpServers } from '../toml-strip-mcp.mjs';
|
|
31
|
+
import { resolveUserConfigDir } from '../../config/user-config-dir.mjs';
|
|
30
32
|
const DEFAULT_SHARED_STATE = {
|
|
31
33
|
// Shared: operator history and session index are cross-profile
|
|
32
34
|
shared: ['history.jsonl', 'sessions'],
|
|
@@ -174,6 +176,9 @@ export function launchWithProfile(profile, extraArgs = [], options = {}) {
|
|
|
174
176
|
...process.env,
|
|
175
177
|
HOME: rtHome,
|
|
176
178
|
CODEX_HOME: rtHome,
|
|
179
|
+
// Resolve before HOME changes so the child retains the operator's
|
|
180
|
+
// installation identity, channel and other provider-neutral configuration.
|
|
181
|
+
AIWG_CONFIG: resolveUserConfigDir(),
|
|
177
182
|
};
|
|
178
183
|
if (isNonInteractiveCodexInvocation(extraArgs)) {
|
|
179
184
|
// Bounded (#3062): `codex exec -` never hands over the terminal; the
|
|
@@ -338,7 +343,7 @@ function spawnCodexLoginWithApiKey(apiKey, env) {
|
|
|
338
343
|
child.removeListener('exit', onExit);
|
|
339
344
|
if (result.error && child.exitCode === null && child.signalCode === null) {
|
|
340
345
|
try {
|
|
341
|
-
child
|
|
346
|
+
signalChild(child, 'SIGKILL');
|
|
342
347
|
}
|
|
343
348
|
catch { /* already gone */ }
|
|
344
349
|
}
|
|
@@ -411,6 +416,7 @@ export async function loginInProfile(profile, options = {}) {
|
|
|
411
416
|
...process.env,
|
|
412
417
|
HOME: rtHome,
|
|
413
418
|
CODEX_HOME: rtHome,
|
|
419
|
+
AIWG_CONFIG: resolveUserConfigDir(),
|
|
414
420
|
};
|
|
415
421
|
// `options.apiKey` (go/no-go review, blocker 1C) is the already-resolved
|
|
416
422
|
// key from `--api-key-stdin`/`--api-key-file` — the caller (`src/mcp/
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ConfigWrite } from './config-file.mjs';
|
|
2
|
+
export function claudeDesktopConfigPath(platform?: string, env?: NodeJS.ProcessEnv): string;
|
|
3
|
+
export function resolveClaudeInstallTarget(target: string, scope: string, env?: NodeJS.ProcessEnv): string;
|
|
4
|
+
export function legacyClaudeMigration(projectDir: string): Promise<ConfigWrite[]>;
|