Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions .config/__tests__/lint-deferrals.unit.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { existsSync, readdirSync, readFileSync } from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

import { describe, expect, it } from 'vitest';

const PACKAGES_DIR = fileURLToPath(new URL('../../packages/', import.meta.url));
const DEFERRED_RULES_PATH = '.config/eslint/deferred-lint-rules.ts';
const PACKAGE_STRICT_LINT_PATH = '.config/strict-lint.config.ts';

// A deferral holds rules the shared preset sets to `error` at `warn`, where they neither block a gate nor get
// fixed. Both halves are checked, because either alone restores the mechanism: the rule list, and the
// `maxSeverity` cap that stops strict-lint promoting those warnings back to errors.
describe('lint deferrals', () => {
it('are declared by no package', () => {
expect(listPackagesCarrying(DEFERRED_RULES_PATH)).toEqual([]);
});

it('are not reintroduced as a strict-lint promotion cap', () => {
const capping = listPackagesCarrying(PACKAGE_STRICT_LINT_PATH).filter((packageName) =>
readFileSync(path.join(PACKAGES_DIR, packageName, PACKAGE_STRICT_LINT_PATH), 'utf8').includes('maxSeverity'),
);

expect(capping).toEqual([]);
});
});

// region | Helpers

/** Names every workspace package holding a file at `relativePath`. */
function listPackagesCarrying(relativePath: string): string[] {
return listWorkspacePackages().filter((packageName) =>
existsSync(path.join(PACKAGES_DIR, packageName, relativePath)),
);
}

/** Names every directory under `packages/` that holds a workspace package. */
function listWorkspacePackages(): string[] {
return readdirSync(PACKAGES_DIR, { withFileTypes: true })
.filter((entry) => entry.isDirectory() && existsSync(path.join(PACKAGES_DIR, entry.name, 'package.json')))
.map((entry) => entry.name)
.toSorted();
}

// endregion | Helpers
12 changes: 0 additions & 12 deletions packages/agents/.config/eslint/deferred-lint-rules.ts

This file was deleted.

10 changes: 0 additions & 10 deletions packages/agents/.config/strict-lint.config.ts

This file was deleted.

2 changes: 2 additions & 0 deletions packages/agents/bin/codeassembly.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#!/usr/bin/env node

// Imports only node builtins: a top-level import resolves before the gate below runs, so an
// unresolvable dependency would replace this file's build-first message with ERR_MODULE_NOT_FOUND.
import { existsSync } from 'node:fs';

// Thin wrapper so pnpm can symlink the bin at install time, before `dist/`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,12 +84,12 @@ describe('collection dispositions', () => {
// it is constrained by no vetted-closure rule.
it('keeps every standalone artifact out of the collections’ combined closure', async () => {
const collections = await readExplicitCollections(contentDir);
const closure = await resolveClosure({ collection: [...collections.keys()] }, libraryResolver(contentDir));
const closure = await resolveClosure({ collection: collections.keys().toArray() }, libraryResolver(contentDir));

const defects = findClosureDefects(
'every collection',
listClosureIds(closure),
[...collections.keys()],
collections.keys().toArray(),
buildClaimMap(collections, Object.keys(STANDALONE)),
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { existsSync } from 'node:fs';
import { readdir, readFile } from 'node:fs/promises';
import path from 'node:path';

import { describeError } from '@williamthorsen/toolbelt.errors/candidate';
import { beforeAll, describe, expect, it } from 'vitest';

import { collectHeadingSlugs, findUnterminatedFence, normalizeForAnchorScan } from '../../src/lib/anchor-resolution.ts';
Expand Down Expand Up @@ -129,7 +130,7 @@ async function findRulebookRejections(): Promise<ReadonlyArray<string>> {
rulebooks,
});
} catch (error) {
rejections.push(error instanceof Error ? error.message : String(error));
rejections.push(describeError(error));
}
}
return rejections;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ function formatViolations(violations: ReadonlyArray<Violation>): string {
const header =
`Found ${violations.length} raw \`${FORBIDDEN_PATTERN}\` reference(s) in installable Markdown. ` +
`Replace each with one of: ` +
`(a) \`{harness_home_dir}/...\` for runtime references the agent reads or executes; ` +
'(a) `{harness_home_dir}/...` for runtime references the agent reads or executes; ' +
`(b) a relative Markdown link \`[text](relative/path.md)\` (the install pipeline rewrites it); ` +
`(c) add the file to the ALLOWLIST in this test if the reference is an intentional source-tree citation. ` +
`See \`packages/agents/content/_partials/README.md\` § "Path references in installed content" for the convention.`;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,10 @@ async function listClosureFiles(contentDir: string): Promise<ReadonlyArray<strin

/** Extracts the content of each backtick-delimited code span on a line. */
function listCodeSpans(text: string): Array<string> {
return [...text.matchAll(CODE_SPAN_PATTERN)].map((match) => match[1] ?? '');
return text
.matchAll(CODE_SPAN_PATTERN)
.map((match) => match[1] ?? '')
.toArray();
}

/** Lists every Markdown file under `relativeDir`, recursively, as paths relative to `root`. */
Expand All @@ -194,7 +197,10 @@ async function listMarkdownFilesUnder(root: string, relativeDir: string): Promis

/** Reads each value a store flag takes within one span of text, in either the spaced or the `=` form. */
function readFlagValues(text: string): Array<string> {
return [...text.matchAll(STORE_FLAG_PATTERN)].flatMap((match) => (match[1] === undefined ? [] : [match[1]]));
return text
.matchAll(STORE_FLAG_PATTERN)
.flatMap((match) => (match[1] === undefined ? [] : [match[1]]))
.toArray();
}

/**
Expand Down
6 changes: 0 additions & 6 deletions packages/agents/eslint.config.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { defineConfig, globalIgnores } from 'eslint/config';

import baseConfig from '../../eslint.config.ts';
import { deferredLintRules } from './.config/eslint/deferred-lint-rules.ts';

const config = defineConfig([
...baseConfig,
Expand All @@ -11,11 +10,6 @@ const config = defineConfig([
'content/skills/**/*.mjs',
'content/skills/**/*-example.ts',
]),
{
files: ['**/*.ts', '**/*.mts', '**/*.tsx', '**/*.md/*.ts', '**/*.js'],
plugins: {},
rules: deferredLintRules,
},
{
files: ['package.json'],
rules: {
Expand Down
1 change: 1 addition & 0 deletions packages/agents/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
},
"dependencies": {
"@williamthorsen/kb": "workspace:*",
"@williamthorsen/toolbelt.errors": "0.2.0",
"codeassembly-lifecycle": "workspace:*",
"ulid": "3.0.2",
"yaml": "2.9.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import { spawn } from 'node:child_process';
import path from 'node:path';
import process from 'node:process';

import { describeError } from '@williamthorsen/toolbelt.errors/candidate';

import { bundleSkillHelpers, type BundleTarget, packageRoot, targets } from '../bundle-skill-helpers.ts';
import {
makeCaptureEventSmokeTest,
Expand Down Expand Up @@ -64,7 +66,7 @@ for (const target of targets) {
console.info(`Smoke test passed: ${target.outFile} exits 0 with valid JSON.`);
} catch (error) {
failed = true;
const message = error instanceof Error ? error.message : String(error);
const message = describeError(error);
console.error(`Smoke test failed: ${target.outFile} — ${message}`);
}
}
Expand Down
2 changes: 1 addition & 1 deletion packages/agents/scripts/testing/smoke-test-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,7 @@ function assertCaptureEventSmokeResult(result: unknown): void {
throw new Error(`expected an ISO-8601 capturedAt, got ${JSON.stringify(result.capturedAt)}`);
}
if (typeof result.path !== 'string' || !result.path.endsWith(`${result.id}.md`)) {
throw new Error(`expected a written record path ending in {id}.md, got ${JSON.stringify(result.path)}`);
throw new Error(`expected a written record path ending in ${result.id}.md, got ${JSON.stringify(result.path)}`);
}
const written = readFileSync(result.path, 'utf8');
if (!/^recordType: event$/m.test(written)) {
Expand Down
7 changes: 4 additions & 3 deletions packages/agents/src/capture-event/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
parseEvent,
renderEvent,
} from '@williamthorsen/kb/records';
import { describeError } from '@williamthorsen/toolbelt.errors/candidate';
import { ulid } from 'ulid';

import { formatMissingStoreMessage } from '../kb-shared/format-missing-store.ts';
Expand Down Expand Up @@ -54,7 +55,7 @@ async function main(): Promise<void> {
});
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
const message = describeError(error);
process.stderr.write(`capture-event: ${message}\n`);
process.exit(1);
}
Expand Down Expand Up @@ -90,7 +91,7 @@ export async function runCapture(input: {
try {
args = parseArgs(input.argv);
} catch (error) {
return { ok: false, error: 'invalid-args', message: error instanceof Error ? error.message : String(error) };
return { ok: false, error: 'invalid-args', message: describeError(error) };
}

const resolved = await resolveCaptureTarget({
Expand Down Expand Up @@ -316,7 +317,7 @@ function isEntryPoint(): boolean {
try {
return realpathSync(fileURLToPath(import.meta.url)) === realpathSync(entry);
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
const message = describeError(error);
process.stderr.write(`capture-event: warning: could not determine entry point: ${message}\n`);
return false;
}
Expand Down
6 changes: 5 additions & 1 deletion packages/agents/src/capture-event/write-event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,11 @@ export async function writeEvent(input: { storePath: string; id: string; content
try {
await link(tempPath, targetPath);
} finally {
await unlink(tempPath).catch(() => {});
try {
await unlink(tempPath);
} catch {
// A temp file left behind is not worth masking the link outcome the caller is waiting on.
}
}

return targetPath;
Expand Down
7 changes: 4 additions & 3 deletions packages/agents/src/capture-lede-decision/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import process from 'node:process';
import type { Readable } from 'node:stream';
import { fileURLToPath } from 'node:url';

import { describeError } from '@williamthorsen/toolbelt.errors/candidate';
import { ulid } from 'ulid';

import { writeEvent } from '../capture-event/write-event.ts';
Expand Down Expand Up @@ -87,7 +88,7 @@ async function main(): Promise<void> {
});
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
const message = describeError(error);
process.stderr.write(`capture-lede-decision: ${message}\n`);
process.exit(1);
}
Expand Down Expand Up @@ -123,7 +124,7 @@ export async function runDecision(input: {
try {
args = parseArgs(input.argv);
} catch (error) {
return { ok: false, error: 'invalid-args', message: error instanceof Error ? error.message : String(error) };
return { ok: false, error: 'invalid-args', message: describeError(error) };
}

const resolved = await resolveEpisode({
Expand Down Expand Up @@ -313,7 +314,7 @@ function isEntryPoint(): boolean {
try {
return realpathSync(fileURLToPath(import.meta.url)) === realpathSync(entry);
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
const message = describeError(error);
process.stderr.write(`capture-lede-decision: warning: could not determine entry point: ${message}\n`);
return false;
}
Expand Down
4 changes: 3 additions & 1 deletion packages/agents/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
/* eslint unicorn/no-process-exit: off */
import process from 'node:process';

import { describeError } from '@williamthorsen/toolbelt.errors/candidate';

import { configureHooksCommand } from './commands/configure-hooks.ts';
import { generateLabelMap, printGenerateUsage } from './commands/generate-label-map.ts';
import { initCommand, initGlobalCommand } from './commands/init.ts';
Expand Down Expand Up @@ -305,7 +307,7 @@ async function runSync(options: InstallOptions, global: boolean, warnOnly: boole
if (!warnOnly) {
throw error;
}
const message = error instanceof Error ? error.message : String(error);
const message = describeError(error);
console.warn(`⚠️ sync failed: ${message}\n Deployed guidance may be stale.`);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import path from 'node:path';

import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';

import { generateLabelMap, printGenerateUsage, readReleaseKitVersion } from '../generate-label-map.js';
import { generateLabelMap, printGenerateUsage, readReleaseKitVersion } from '../generate-label-map.ts';

interface LabelMap {
readonly $schema: string;
Expand Down
8 changes: 3 additions & 5 deletions packages/agents/src/commands/__tests__/install.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,11 +187,9 @@ describe(installCommand, () => {

const manifest = await readManifest(getManifestPath(tempDir));
const entries = manifest.harnesses.claude?.entries ?? [];
for (const entry of entries.filter((e) => e.relativePath.startsWith('skills/'))) {
expect(entry.linked).toBe(false);
}
for (const entry of entries.filter((e) => e.relativePath.startsWith('scripts/'))) {
expect(entry.linked).toBe(true);
for (const entry of entries) {
if (entry.relativePath.startsWith('skills/')) expect(entry.linked).toBe(false);
if (entry.relativePath.startsWith('scripts/')) expect(entry.linked).toBe(true);
}
});

Expand Down
19 changes: 9 additions & 10 deletions packages/agents/src/commands/install.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import { chmod, mkdir, readdir, readFile, stat, writeFile } from 'node:fs/promises';
import path from 'node:path';

import { describeError } from '@williamthorsen/toolbelt.errors/candidate';

import { extractAmbientRegionContent, hasAmbientRegion, injectAmbientRegion } from '../lib/ambient-region.ts';
import { assertAnchorsResolve } from '../lib/anchor-resolution.ts';
import { resolveContentDir } from '../lib/content-resolver.ts';
import { expandIncludes } from '../lib/directive-expander.ts';
import { emitReport } from '../lib/emit-report.ts';
import { describePruneResult, pruneOrphanedEntries } from '../lib/entry-remover.ts';
import { stripGuidanceHooks } from '../lib/guidance-hooks.ts';
import { HARNESSES, resolveHarnessIds, resolveHarnessPaths, resolveSkillsPathPrefix } from '../lib/harness.js';
import { HARNESSES, resolveHarnessIds, resolveHarnessPaths, resolveSkillsPathPrefix } from '../lib/harness.ts';
import { loadHarnessOverlay } from '../lib/harness-overlay.ts';
import { recordHomeProvenance } from '../lib/home-provenance.ts';
import { assertDesignatedWriter } from '../lib/home-writer-guard.ts';
Expand All @@ -21,12 +23,12 @@ import {
readManifest,
resolveSharedHome,
writeManifest,
} from '../lib/manifest.js';
import { buildSourceUrl, injectMarkerInFile, injectMarkersInDirectory } from '../lib/marker-injector.js';
import { homeAnchor, rewritePathsInFile } from '../lib/path-rewriter.js';
} from '../lib/manifest.ts';
import { buildSourceUrl, injectMarkerInFile, injectMarkersInDirectory } from '../lib/marker-injector.ts';
import { homeAnchor, rewritePathsInFile } from '../lib/path-rewriter.ts';
import { readRunningPackageVersion, resolveRunningPackageRoot } from '../lib/running-package.ts';
import { type RenderedSkillEntry, renderSupportEntry } from '../lib/skill-transform.ts';
import { loadToolMapping } from '../lib/tool-name-rewriter.js';
import { loadToolMapping } from '../lib/tool-name-rewriter.ts';
import { isEnoent } from '../lib/type-guards.ts';
import type {
AgentsManifest,
Expand All @@ -36,7 +38,7 @@ import type {
InstallOptions,
ManifestEntry,
SharedManifest,
} from '../lib/types.js';
} from '../lib/types.ts';
import { ensureHarnessHookEntries } from './configure-hooks.ts';

/**
Expand Down Expand Up @@ -148,10 +150,7 @@ export async function installCommand(
try {
await ensureHarnessHookEntries(harnessId, baseDir);
} catch (error) {
console.warn(
` ⚠️ Skipping hook wiring: ${error instanceof Error ? error.message : String(error)} ` +
'(fix the config, then run configure-hooks)',
);
console.warn(` ⚠️ Skipping hook wiring: ${describeError(error)} (fix the config, then run configure-hooks)`);
}
}
}
Expand Down
Loading