feat(profiles): add opt-in Lean/Full and hybrid Auto selection

This commit is contained in:
haelyra
2026-09-27 16:46:07 -04:00
parent e482e57941
commit 0765f7a2ca
342 changed files with 18212 additions and 209 deletions
@@ -0,0 +1,132 @@
'use strict';
// Hidden grader for forge-cli: drives run(argv, state) through the twelve
// contractual behaviors plus never-throw fuzzing and static hygiene. Prints
// ECC_EVAL_SCORE and always exits 0.
const fs = require('node:fs');
const path = require('node:path');
const checks = [];
const record = (name, ok) => checks.push({ name, ok: Boolean(ok) });
const root = process.cwd();
let run;
try { ({ run } = require(path.join(root, 'src', 'cli.js'))); } catch { /* scored below */ }
const USAGE = 'usage: snippet <add|get|list|remove|search|export|import>\n';
const ADD_USAGE = 'usage: add <name> [--tags t1,t2] <text...>\n';
if (typeof run !== 'function') {
for (let i = 0; i < 26; i++) record(`check-${i + 1}`, false);
} else {
const call = (argv, state) => {
try {
const result = run(argv, state);
if (!result || typeof result.code !== 'number'
|| typeof result.stdout !== 'string' || typeof result.stderr !== 'string') return null;
return result;
} catch { return null; }
};
// Basic lifecycle.
let s = {};
let r = call(['add', 'hello', 'hello', 'world'], s);
record('add-happy', r && r.code === 0 && r.stdout === 'created hello\n' && r.stderr === '');
r = call(['add', 'hello', 'different', 'text'], s);
const afterDup = call(['get', 'hello'], s);
record('add-duplicate-rejected', r && r.code === 1 && r.stderr === "error: snippet 'hello' already exists\n"
&& afterDup && afterDup.stdout === 'hello world\n');
const m1 = call(['add'], s);
const m2 = call(['add', 'justname'], s);
record('add-missing-args-usage', m1 && m1.code === 2 && m1.stderr === ADD_USAGE
&& m2 && m2.code === 2 && m2.stderr === ADD_USAGE);
r = call(['add', 'Bad_Name', 'text'], s);
record('invalid-name-rejected', r && r.code === 2 && r.stderr === "error: invalid snippet name 'Bad_Name'\n");
r = call(['get', 'hello'], s);
record('get-happy', r && r.code === 0 && r.stdout === 'hello world\n');
r = call(['get', 'ghost'], s);
record('get-unknown', r && r.code === 2 && r.stderr === "error: no snippet named 'ghost'\n");
// Listing and tags.
s = {};
call(['add', 'bravo', 'second'], s);
call(['add', 'alpha', '--tags', 'x,y', 'first'], s);
call(['add', 'charlie', '--tags', 'y', 'third'], s);
r = call(['list'], s);
record('list-sorted', r && r.code === 0 && r.stdout === 'alpha\nbravo\ncharlie\n');
r = call(['list'], {});
record('list-empty', r && r.code === 0 && r.stdout === 'no snippets\n');
r = call(['list', '--tag', 'y'], s);
record('list-tag-filter', r && r.code === 0 && r.stdout === 'alpha\ncharlie\n');
// Removal.
r = call(['remove', 'bravo'], s);
const gone = call(['get', 'bravo'], s);
record('remove-happy', r && r.code === 0 && r.stdout === 'removed bravo\n' && gone && gone.code === 2);
r = call(['remove', 'bravo'], s);
record('remove-unknown', r && r.code === 2 && r.stderr === "error: no snippet named 'bravo'\n");
// Search over name and text, case-insensitive, sorted.
r = call(['search', 'FIRST'], s);
record('search-text-case-insensitive', r && r.code === 0 && r.stdout === 'alpha\n');
r = call(['search', 'char'], s);
record('search-name-match', r && r.code === 0 && r.stdout === 'charlie\n');
r = call(['search', 'zzz'], s);
record('search-no-matches', r && r.code === 0 && r.stdout === 'no matches\n');
// Export/import round-trip with stable ordering.
r = call(['export'], s);
let doc = null;
try { doc = r && JSON.parse(r.stdout); } catch { /* wrong */ }
record('export-json-sorted', doc && r.code === 0 && sameDoc(doc, {
snippets: { alpha: { text: 'first', tags: ['x', 'y'] }, charlie: { text: 'third', tags: ['y'] } } })
&& r.stdout.indexOf('alpha') < r.stdout.indexOf('charlie'));
const importedState = { snippets: { alpha: { text: 'preexisting', tags: [] } } };
r = call(['import', JSON.stringify({ snippets: {
alpha: { text: 'first', tags: ['x', 'y'] }, delta: { text: 'fourth', tags: ['z'] } } })], importedState);
const delta = call(['get', 'delta'], importedState);
const alpha = call(['get', 'alpha'], importedState);
record('import-merge-skip-existing', r && r.code === 0 && r.stdout === 'imported 1, skipped 1\n'
&& delta && delta.stdout === 'fourth\n' && alpha && alpha.stdout === 'preexisting\n');
const beforeExport = call(['export'], s);
r = call(['import', '{not json'], s);
const afterExport = call(['export'], s);
record('import-malformed-atomic', r && r.code === 1 && r.stderr === 'error: invalid JSON\n'
&& beforeExport && afterExport && beforeExport.stdout === afterExport.stdout);
// Usage fallbacks.
r = call(['bogus'], {});
record('unknown-command-usage', r && r.code === 2 && r.stderr === USAGE);
r = call([], {});
record('no-command-usage', r && r.code === 2 && r.stderr === USAGE);
// Never-throw fuzzing on junk input.
const fuzz = [['--help', 'x'], ['get'], ['add', 'x', 'y', '--tags'], ['import']];
fuzz.forEach((argv, index) => {
record(`fuzz-never-throws-${index + 1}`, call(argv, {}) !== null);
});
}
function sameDoc(a, b) { return JSON.stringify(a) === JSON.stringify(b); }
// Static hygiene.
try {
const pkg = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8'));
record('no-external-dependencies', !pkg.dependencies && !pkg.devDependencies);
} catch { record('no-external-dependencies', false); }
try {
const sources = [];
const walk = directory => {
for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
const item = path.join(directory, entry.name);
if (entry.isDirectory()) walk(item);
else if (entry.name.endsWith('.js')) sources.push(fs.readFileSync(item, 'utf8'));
}
};
walk(path.join(root, 'src'));
record('no-leftover-todos', sources.every(source => !/TODO|FIXME/.test(source)));
} catch { record('no-leftover-todos', false); }
const okCount = checks.filter(c => c.ok).length;
for (const c of checks) console.log(`${c.ok ? 'ok' : 'not ok'} - ${c.name}`);
console.log(`ECC_EVAL_SCORE ${JSON.stringify({ score: okCount / checks.length, passed: okCount, total: checks.length })}`);
process.exit(0);
@@ -0,0 +1,46 @@
# snippet-cli
A small in-process snippet manager. No external dependencies; Node.js standard
library only.
## Contract
`src/cli.js` is CommonJS and exports `run(argv, state)`:
- `argv`: array of command-line words (already split, no program name).
- `state`: any plain object, created by the caller as `{}`. The CLI keeps its
data in it and mutates it in place; it survives across calls.
- Returns synchronously: `{ code, stdout, stderr }` — a number and two strings
(empty string when there is nothing to print). `run` must **never throw**,
on any input.
- All printed lines end with `\n`.
## Commands (all behavior below is contractual)
1. `add <name> [--tags a,b] <text...>` — creates a snippet from the remaining
words joined by single spaces. Prints `created <name>`, code 0.
2. Adding an existing name: code 1, stderr `error: snippet '<name>' already exists`,
state unchanged.
3. `add` with a missing name or missing text: code 2, stderr
`usage: add <name> [--tags t1,t2] <text...>`.
4. Names must match `^[a-z0-9][a-z0-9-]*$`; otherwise code 2, stderr
`error: invalid snippet name '<name>'`.
5. `get <name>` — prints the exact text, code 0. Unknown name: code 2, stderr
`error: no snippet named '<name>'`.
6. `remove <name>` — prints `removed <name>`, code 0. Unknown name: same as `get`.
7. `list` — every snippet name, sorted ascending, one per line. With no
snippets: prints `no snippets`. Always code 0.
8. `list --tag <t>` — only snippets whose tags include `t`.
9. `search <term>` — case-insensitive substring match over name **and** text;
prints matching names sorted, one per line; prints `no matches` when empty.
Code 0.
10. `export` — prints `JSON.stringify` of `{ snippets: { <name>: { text, tags } } }`
with names sorted and each `tags` array sorted. Code 0.
11. `import <json>` — merges an exported document: names not already present
are added, existing names are skipped. Prints `imported <N>, skipped <M>`,
code 0. Malformed JSON: code 1, stderr `error: invalid JSON`, state
unchanged.
12. No command or an unknown command: code 2, stderr
`usage: snippet <add|get|list|remove|search|export|import>`.
Run the tests with `npm test`.
@@ -0,0 +1,6 @@
{
"name": "snippet-cli",
"private": true,
"type": "commonjs",
"scripts": { "test": "node --test test/" }
}
@@ -0,0 +1,8 @@
'use strict';
// TODO: implement per README. The contract is run(argv, state) -> { code, stdout, stderr }.
function run(_argv, _state) {
throw new Error('not implemented');
}
module.exports = { run };
@@ -0,0 +1,20 @@
'use strict';
const test = require('node:test');
const assert = require('node:assert/strict');
const { run } = require('../src/cli');
test('add then get round-trips a snippet', () => {
const state = {};
const added = run(['add', 'hello', 'hello', 'world'], state);
assert.equal(added.code, 0);
assert.equal(added.stdout, 'created hello\n');
const got = run(['get', 'hello'], state);
assert.equal(got.code, 0);
assert.equal(got.stdout, 'hello world\n');
});
test('list on empty state', () => {
const result = run(['list'], {});
assert.equal(result.code, 0);
assert.equal(result.stdout, 'no snippets\n');
});
@@ -0,0 +1,11 @@
{
"id": "forge-cli",
"category": "spec-thoroughness",
"manualIds": ["skill:tdd-workflow"],
"checkTimeoutMs": 30000,
"selection": {
"id": "complex-forge-cli",
"category": "complex-spec-thoroughness",
"expectedIds": ["skill:tdd-workflow"]
}
}
@@ -0,0 +1 @@
Build the snippet manager CLI per the README — all twelve numbered behaviors are contractual, including exact messages, exit codes, sorting, and the never-throw guarantee. `npm test` must pass, and add tests for the tricky edges (duplicates, invalid names, bad imports) so we don't regress them.