@damiangilgonzalez/harness 1.3.17 → 1.3.19
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 +117 -657
- package/manifest.json +1 -1
- package/package.json +1 -1
- package/scripts/doctor.js +1 -1
- package/scripts/install-apply.js +9 -0
- package/scripts/rsc.js +90 -2
- package/skills/building-agents/SKILL.md +1 -1
- package/skills/grill-with-docs/SKILL.md +0 -1
- package/skills/harness/references/daily-curation-automation.md +2 -1
- package/skills/harness/references/wiki-protocol.md +3 -1
- package/skills/implement/SKILL.md +0 -1
- package/skills/implement-spec/SKILL.md +1 -2
- package/skills/init/SKILL.md +7 -5
- package/skills/postgresdb/SKILL.md +1 -1
- package/skills/suggest/SKILL.md +17 -13
- package/skills/suggest/evals/cases.yaml +6 -0
- package/skills/to-spec/SKILL.md +0 -1
- package/skills/to-tickets/SKILL.md +0 -1
- package/skills/wayfinder/SKILL.md +0 -1
- package/targets/_md-block.js +1 -1
- package/targets/claude.js +2 -1
- package/targets/clone-bootstrap.mjs +1 -1
- package/targets/danger-guard.mjs +114 -52
- package/targets/hook-once.mjs +44 -13
- package/targets/knowledge-sync.mjs +759 -0
- package/targets/knowledge-wiring.js +196 -0
- package/targets/memory.js +7 -1
- package/targets/opencode-memory-plugin.js +67 -22
- package/targets/opt-outs.js +4 -0
- package/targets/session-memory-adapter.mjs +51 -6
- package/targets/session-memory-core.mjs +284 -37
- package/targets/session-start.mjs +2 -1
- package/targets/ship-guard.mjs +26 -9
- package/targets/trunk-policy.mjs +86 -0
- package/targets/userprompt-gate.mjs +7 -2
- package/targets/worklog-checkpoint.mjs +16 -1
- package/targets/worktree-reaper.mjs +144 -4
package/manifest.json
CHANGED
package/package.json
CHANGED
package/scripts/doctor.js
CHANGED
|
@@ -261,7 +261,7 @@ export function doctor({ target, home, cwd }) {
|
|
|
261
261
|
// #277 — the three "missing" lists share one shape, { id, path, action }, so whoever builds on top
|
|
262
262
|
// of doctor reads them the same way; and `healthy` is what the exit code says.
|
|
263
263
|
for (const [id, e] of Object.entries(state.skills)) {
|
|
264
|
-
for (const f of e.files) if (!existsSync(f)) report.missing.push({ id, path: f, action: 'Run `npx @
|
|
264
|
+
for (const f of e.files) if (!existsSync(f)) report.missing.push({ id, path: f, action: 'Run `npx @damiangilgonzalez/harness sync` to restore this managed skill.' });
|
|
265
265
|
}
|
|
266
266
|
report.healthy = report.hookWired && !report.missing.length && !missingAgents.length && !missingCommands.length;
|
|
267
267
|
return report;
|
package/scripts/install-apply.js
CHANGED
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
import {
|
|
22
22
|
wireMemory, unwireMemory, memoryManagedPaths, memoryModeFor, memoryEnabledForProject, memoryArtifactsPresent,
|
|
23
23
|
} from '../targets/memory.js';
|
|
24
|
+
import { wireKnowledge, unwireKnowledge, knowledgeManagedPaths, knowledgeArtifactsPresent } from '../targets/knowledge-wiring.js';
|
|
24
25
|
|
|
25
26
|
const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..');
|
|
26
27
|
const CLI_VERSION = JSON.parse(readFileSync(join(ROOT, 'package.json'), 'utf8')).version;
|
|
@@ -94,6 +95,7 @@ export function managedPathsForInstall({ skillIds, agentIds = [], target, home,
|
|
|
94
95
|
const out = [paths.stateFile, versionFile(cwd), baseVersionsFile(cwd)];
|
|
95
96
|
if (policy?.context7 === false) out.push(join(cwd, '.rsc', '.no-context7'));
|
|
96
97
|
if (policy?.memory !== false) out.push(...memoryManagedPaths(target, cwd));
|
|
98
|
+
out.push(...knowledgeManagedPaths(target, cwd));
|
|
97
99
|
if (targetHasAgents(target)) {
|
|
98
100
|
const state = readState(paths.stateFile);
|
|
99
101
|
const explicit = [...new Set([...(state.explicitAgents || readManifest(cwd)?.agents || []), ...agentIds])];
|
|
@@ -273,6 +275,10 @@ export async function applyInstall({ skillIds = [], agentIds = [], target, home,
|
|
|
273
275
|
? { mode: 'disabled', reason: 'onboarding-policy', paths: unwireMemory(target, cwd) }
|
|
274
276
|
: wireMemory(target, cwd);
|
|
275
277
|
state.memory = { mode: memoryResult.mode, reason: memoryResult.reason, paths: memoryResult.paths };
|
|
278
|
+
// Knowledge sync is its own decision (`.no-knowledge-sync`, checked at runtime) and never part of
|
|
279
|
+
// the memory, so the memory's policy does not decide it: wired wherever the assistant can run it.
|
|
280
|
+
const knowledgeResult = wireKnowledge(target, cwd);
|
|
281
|
+
state.knowledge = { mode: knowledgeResult.mode, reason: knowledgeResult.reason || null, paths: knowledgeResult.paths };
|
|
276
282
|
const context7OptOut = join(cwd, '.rsc', '.no-context7');
|
|
277
283
|
if (policy?.context7 === false) {
|
|
278
284
|
mkdirSync(dirname(context7OptOut), { recursive: true });
|
|
@@ -587,6 +593,7 @@ export function removeTargetInstall({ target, home, cwd = process.cwd() }) {
|
|
|
587
593
|
}
|
|
588
594
|
unwireHook(target, paths);
|
|
589
595
|
unwireMemory(target, cwd);
|
|
596
|
+
unwireKnowledge(target, cwd);
|
|
590
597
|
rmSync(paths.stateFile, { force: true });
|
|
591
598
|
const gitignore = join(cwd, '.gitignore');
|
|
592
599
|
if (existsSync(gitignore)) {
|
|
@@ -731,8 +738,10 @@ export async function purge({ home, cwd = process.cwd(), withDocs = false, dryRu
|
|
|
731
738
|
if (!dryRun) {
|
|
732
739
|
removed.push(...unwireHook(target, paths));
|
|
733
740
|
removed.push(...unwireMemory(target, cwd));
|
|
741
|
+
removed.push(...unwireKnowledge(target, cwd));
|
|
734
742
|
} else {
|
|
735
743
|
removed.push(...memoryArtifactsPresent(target, cwd));
|
|
744
|
+
removed.push(...knowledgeArtifactsPresent(target, cwd));
|
|
736
745
|
}
|
|
737
746
|
// A lost state file also loses proof of ownership. Leave same-named user files
|
|
738
747
|
// behind rather than guessing from a catalog id and deleting their work.
|
package/scripts/rsc.js
CHANGED
|
@@ -41,7 +41,7 @@ if (['--version', '-v', 'version'].includes(rawArgv[0])) {
|
|
|
41
41
|
const GLOBAL_VALUE_FLAGS = new Set([
|
|
42
42
|
'--target', '--technical-level', '--accompaniment', '--project-kind', '--goal', '--goal-base64', '--software-scope', '--accept-plan',
|
|
43
43
|
]);
|
|
44
|
-
const COMMANDS = new Set(['onboard', 'reassess', 'add', 'install', 'consult', 'catalog', 'audit', 'list', 'doctor', 'memory', 'sync', 'backups', 'restore', 'upgrade', 'registry', 'worktrees', 'capabilities', 'review', 'sello', 'repair', 'uninstall', 'purge']);
|
|
44
|
+
const COMMANDS = new Set(['onboard', 'reassess', 'add', 'install', 'consult', 'catalog', 'audit', 'list', 'doctor', 'memory', 'knowledge-sync', 'sync', 'backups', 'restore', 'upgrade', 'registry', 'worktrees', 'capabilities', 'review', 'sello', 'repair', 'uninstall', 'purge']);
|
|
45
45
|
function positionalTokens(input) {
|
|
46
46
|
const out = [];
|
|
47
47
|
for (let i = 0; i < input.length; i++) {
|
|
@@ -756,6 +756,32 @@ async function main() {
|
|
|
756
756
|
printContextBudget(report.contextBudget);
|
|
757
757
|
return void say(JSON.stringify({ ...report, contextBudget: undefined }, null, 2));
|
|
758
758
|
}
|
|
759
|
+
case 'knowledge-sync': {
|
|
760
|
+
// On by default; the switch is a PROJECT decision (`.no-knowledge-sync`, recorded in `.rsc.json`
|
|
761
|
+
// optOuts by the sync below), because one person off means the team stops seeing their work.
|
|
762
|
+
const sub = argv[1] || 'status';
|
|
763
|
+
const root = process.cwd();
|
|
764
|
+
const { mkdirSync, writeFileSync, rmSync } = await import('node:fs');
|
|
765
|
+
const { join } = await import('node:path');
|
|
766
|
+
const marker = join(root, '.rsc', '.no-knowledge-sync');
|
|
767
|
+
if (sub === 'on' || sub === 'off') {
|
|
768
|
+
mkdirSync(join(root, '.rsc'), { recursive: true });
|
|
769
|
+
if (sub === 'off') writeFileSync(marker, '');
|
|
770
|
+
else rmSync(marker, { force: true });
|
|
771
|
+
for (const t of targets) await syncInstalled({ target: t, cwd: root });
|
|
772
|
+
say(sub === 'off'
|
|
773
|
+
? 'rsc knowledge-sync off: 01-TOOLS/ and docs/ no longer sync on their own in this project. Commit .rsc.json so the team gets the same decision.'
|
|
774
|
+
: 'rsc knowledge-sync on: 01-TOOLS/ and docs/ go up when a turn ends and come down before each message. Commit .rsc.json so the team gets the same decision.');
|
|
775
|
+
return;
|
|
776
|
+
}
|
|
777
|
+
if (sub === 'status') {
|
|
778
|
+
const { knowledgeStatus } = await import('../targets/knowledge-sync.mjs');
|
|
779
|
+
return void say(JSON.stringify(knowledgeStatus(root), null, 2));
|
|
780
|
+
}
|
|
781
|
+
say('Use: npx @damiangilgonzalez/harness@latest knowledge-sync on|off|status');
|
|
782
|
+
process.exitCode = 2;
|
|
783
|
+
return;
|
|
784
|
+
}
|
|
759
785
|
case 'memory': {
|
|
760
786
|
const sub = argv[1] || 'status';
|
|
761
787
|
const root = process.cwd();
|
|
@@ -885,11 +911,72 @@ async function main() {
|
|
|
885
911
|
const root = W.resolveMainRoot(process.cwd());
|
|
886
912
|
const sub = argv[1];
|
|
887
913
|
|
|
914
|
+
if (sub === 'new') {
|
|
915
|
+
// Creating is not cleaning, so the cleanup opt-out does not apply here. Two things a worktree
|
|
916
|
+
// was measured lacking when it is born: a current base (local main was 400 commits behind
|
|
917
|
+
// origin) and what git does not track (.env, dependencies). The base is fixed here; the rest
|
|
918
|
+
// is the project's own hook, because only the project knows what makes it runnable.
|
|
919
|
+
const branchAt = argv.indexOf('--branch');
|
|
920
|
+
const branch = branchAt >= 0 ? argv[branchAt + 1] : undefined;
|
|
921
|
+
if (branchAt >= 0 && (!branch || branch.startsWith('--'))) { say('rsc: --branch needs a branch name.'); process.exitCode = 1; return; }
|
|
922
|
+
const name = argv.slice(2).find((a, i, all) => !a.startsWith('--') && all[i - 1] !== '--branch');
|
|
923
|
+
if (!name) { say('Use: npx @damiangilgonzalez/harness worktrees new <nombre> [--branch <rama>]'); process.exitCode = 1; return; }
|
|
924
|
+
const out = W.createWorktree(root, name, { branch });
|
|
925
|
+
if (!out.created) { say(`rsc: ${out.reason}`); process.exitCode = 1; return; }
|
|
926
|
+
if (!out.fetched) say(`⚠️ git fetch origin failed (${out.fetchError || 'no detail'}); starting from the last known ${out.base}, which may be stale.`);
|
|
927
|
+
say(`✅ worktree ${out.path}`);
|
|
928
|
+
say(` branch ${out.branch}, from ${out.base}`);
|
|
929
|
+
const hook = W.findPrepareHook(out.path, root);
|
|
930
|
+
if (hook) {
|
|
931
|
+
say(` preparing it: ${hook}`);
|
|
932
|
+
const ran = W.runPrepareHook(hook, out.path, root);
|
|
933
|
+
if (!ran.ok) { say(`⚠️ the preparation hook failed (${ran.reason}). The worktree exists; finish preparing it by hand.`); process.exitCode = 1; }
|
|
934
|
+
} else {
|
|
935
|
+
say(` No preparation hook (${W.PREPARE_HOOKS.join(' or ')}): .env files and dependencies are NOT copied — create the hook to do it on every new worktree.`);
|
|
936
|
+
}
|
|
937
|
+
say(`Next: cd "${out.path}"`);
|
|
938
|
+
return;
|
|
939
|
+
}
|
|
940
|
+
|
|
888
941
|
if (!W.isCleanupEnabled(root)) {
|
|
889
942
|
say(`worktree cleanup is off for this project (.rsc/${W.OPT_OUT}). Delete that file to turn it back on.`);
|
|
890
943
|
return;
|
|
891
944
|
}
|
|
892
945
|
|
|
946
|
+
if (sub === 'reap' && argv.includes('--merged')) {
|
|
947
|
+
// The bulk answer to "delete the worktrees" — asked four times in one project, with up to
|
|
948
|
+
// fourteen open. Merged is judged against origin's trunk AFTER a fetch, so a branch landed in
|
|
949
|
+
// the forge an hour ago counts. Without --confirm this only lists: one look, then one yes.
|
|
950
|
+
const fetched = W.fetchOrigin(root);
|
|
951
|
+
if (!fetched.ok) say(`⚠️ git fetch origin failed (${fetched.err || 'no detail'}); judging against the last known trunk.`);
|
|
952
|
+
const all = W.classifyWorktrees(root);
|
|
953
|
+
const merged = W.mergedCandidates(all);
|
|
954
|
+
const held = all.filter((c) => c.verdict === 'ask' && !merged.includes(c));
|
|
955
|
+
if (!merged.length) say('No merged worktree that can be removed in bulk.');
|
|
956
|
+
if (!argv.includes('--confirm')) {
|
|
957
|
+
if (merged.length) {
|
|
958
|
+
say(`${merged.length} merged worktree(s), clean, removable in one go:`);
|
|
959
|
+
for (const c of merged) say(` ${c.path}${c.branch ? ` (${c.branch})` : ''}`);
|
|
960
|
+
}
|
|
961
|
+
for (const c of held) say(W.describe(c));
|
|
962
|
+
if (merged.length) say('\nRemove them all: npx @damiangilgonzalez/harness worktrees reap --merged --confirm');
|
|
963
|
+
return;
|
|
964
|
+
}
|
|
965
|
+
for (const c of merged) {
|
|
966
|
+
const out = W.reapWorktree(root, c.path, { confirmed: true });
|
|
967
|
+
if (out.removed) {
|
|
968
|
+
say(`✅ removed ${out.path}`);
|
|
969
|
+
if (out.branchKept) say(` branch ${out.branch} kept — git will not delete it safely, and while it exists the commits are recoverable.`);
|
|
970
|
+
} else {
|
|
971
|
+
say(`⏸ kept ${c.path} — ${out.reason}`);
|
|
972
|
+
process.exitCode = 1;
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
// What a bulk yes never covers, said out loud so nobody thinks it was forgotten.
|
|
976
|
+
for (const c of held) say(`⏸ kept ${c.path} — ${W.refusal(c)}`);
|
|
977
|
+
return;
|
|
978
|
+
}
|
|
979
|
+
|
|
893
980
|
const candidates = W.classifyWorktrees(root).filter((c) => c.verdict !== 'skip');
|
|
894
981
|
|
|
895
982
|
if (sub === 'reap') {
|
|
@@ -920,6 +1007,7 @@ async function main() {
|
|
|
920
1007
|
say('');
|
|
921
1008
|
say('Remove the safe ones: npx @damiangilgonzalez/harness worktrees reap');
|
|
922
1009
|
say('Remove one by name: npx @damiangilgonzalez/harness worktrees reap <path> [--confirm]');
|
|
1010
|
+
say('Remove every merged: npx @damiangilgonzalez/harness worktrees reap --merged [--confirm]');
|
|
923
1011
|
return;
|
|
924
1012
|
}
|
|
925
1013
|
case 'capabilities': {
|
|
@@ -1195,7 +1283,7 @@ async function main() {
|
|
|
1195
1283
|
return void (await runPurge(argv.includes('--dry-run'), argv.includes('--with-docs')));
|
|
1196
1284
|
default:
|
|
1197
1285
|
say(`rsc: unknown command '${cmd}'.`);
|
|
1198
|
-
say('Use: npx @damiangilgonzalez/harness onboard | reassess | add <id...> | install --profile <p> | consult "<text>" | list | capabilities [--full|gap-log] | audit | registry refresh | doctor | sync | memory <on|off|status|save|resume|learn|metrics> | sello <on|off|status|…> | worktrees [reap [path] [--confirm]] | backups | restore <id|latest> | upgrade | repair | uninstall <id> | purge');
|
|
1286
|
+
say('Use: npx @damiangilgonzalez/harness onboard | reassess | add <id...> | install --profile <p> | consult "<text>" | list | capabilities [--full|gap-log] | audit | registry refresh | doctor | sync | memory <on|off|status|save|resume|learn|metrics> | sello <on|off|status|…> | worktrees [new <nombre> [--branch <rama>] | reap [path|--merged] [--confirm]] | backups | restore <id|latest> | upgrade | repair | uninstall <id> | purge');
|
|
1199
1287
|
say('Any command takes --target <claude|codex|cursor|copilot|gemini|…> (comma-separate for several)');
|
|
1200
1288
|
say(' → without it, rsc uses the assistant already installed here; if two are, it asks instead of guessing.');
|
|
1201
1289
|
process.exitCode = 1;
|
|
@@ -14,7 +14,7 @@ A thin provider adapter, a disciplined agent loop, schema-validated tools, provi
|
|
|
14
14
|
|
|
15
15
|
> Program against a **capability interface**, never a vendor SDK. Vendor specifics (model id, tool-schema shape, JSON mode, caching, token limits) live behind one adapter resolved from config. Model names and prices rot — if one appears in business logic it's a bug, and re-verify the dated tables before quoting a number.
|
|
16
16
|
|
|
17
|
-
**Hand off instead when:** a new non-trivial feature has no approved spec + plan under `docs/wiki/sdd/` → stop and write one first (the `to-spec` skill,
|
|
17
|
+
**Hand off instead when:** a new non-trivial feature has no approved spec + plan under `docs/wiki/sdd/` → stop and write one first (the `to-spec` skill, then `to-tickets`), which routes back here once the plan is approved; one-line/low-risk changes go straight through. Anthropic-SDK internals (caching, thinking, batch) in a file that *only* imports `anthropic` → `claude-api` if your environment has it, since this skill stays multi-provider. Workspace scaffolding → [`harness`](../harness/SKILL.md). Choosing *which* coding agent to use → agent-eval territory. Pure prompt-wording tuning with no architecture change → prompt engineering, not this. A one-shot throwaway prompt, or no retrieval/tools/loop/evals at all → you don't need an agent; call the SDK directly and say so.
|
|
18
18
|
|
|
19
19
|
## Decision rules (read before writing code)
|
|
20
20
|
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
name: grill-with-docs
|
|
3
3
|
description: "A relentless interrogation to sharpen a plan or design, which also builds documentation (ADRs and a glossary) along the way."
|
|
4
4
|
tags: ["planning", "questioning", "documentation", "adr"]
|
|
5
|
-
disable-model-invocation: true
|
|
6
5
|
profiles: [core, ui, full]
|
|
7
6
|
origin: damiangil
|
|
8
7
|
---
|
|
@@ -42,7 +42,8 @@ process inbox/, then scan the workspace (minus .rscignore) for un-ingested docum
|
|
|
42
42
|
applying Clean-as-you-go (move loose root-level + inbox files into raw/ after a
|
|
43
43
|
verified ingest; copy files nested in user-maintained folders and propose
|
|
44
44
|
consolidation), record them in wiki/.ingested.json, and list ambiguous folders as
|
|
45
|
-
proposals (do not grab them). Then compile pending raw/worklog/ material
|
|
45
|
+
proposals (do not grab them). Then compile pending raw/worklog/ material (skip this
|
|
46
|
+
step if raw/worklog/ does not exist; do not list a missing folder) into wiki/
|
|
46
47
|
pages (update existing before creating new), keep frontmatter + relative markdown
|
|
47
48
|
links OKF-consistent, append significant decisions to wiki/harness/decisions.md,
|
|
48
49
|
refresh the .base-backed navigation, recompute scores.json + the score: property,
|
|
@@ -417,7 +417,9 @@ safety rule the curation pass honors.
|
|
|
417
417
|
### Steps
|
|
418
418
|
|
|
419
419
|
1. **Capture.** Write the worklog to `raw/worklog/YYYY-MM-DD-<slug>.md`
|
|
420
|
-
(`status: unprocessed`). Infer `topic` from content.
|
|
420
|
+
(`status: unprocessed`). Infer `topic` from content. If `raw/worklog/` does
|
|
421
|
+
not exist yet, create it (`mkdir -p`) — never list it blindly: the checkpoint
|
|
422
|
+
hook already says whether it exists and how many worklogs it holds.
|
|
421
423
|
2. **Compile.** Run the normal Compile (Fetch is a no-op — the worklog is already
|
|
422
424
|
`raw`): distill durable points into `wiki/<topic>/` articles (update existing
|
|
423
425
|
before creating new), add markdown-link cross-refs + `## Related`, cascade.
|
|
@@ -2,12 +2,11 @@
|
|
|
2
2
|
name: implement-spec
|
|
3
3
|
description: "Implement the result of /to-spec and /to-tickets in code, orchestrating parallel implementer subagents over the ticket graph."
|
|
4
4
|
tags: ["implementation", "spec", "tickets", "subagents", "orchestration"]
|
|
5
|
-
disable-model-invocation: true
|
|
6
5
|
profiles: [core, ui, full]
|
|
7
6
|
origin: damiangil
|
|
8
7
|
---
|
|
9
8
|
|
|
10
|
-
Te han dado una spec. Esa spec debería tener tickets asociados que describen cómo implementarla.
|
|
9
|
+
Te han dado una spec. Esa spec debería tener tickets asociados que describen cómo implementarla. Si no los tiene, **para**: lanza `to-tickets` sobre la spec y vuelve aquí con el grafo ya publicado. Sin tickets no hay frontera que repartir, y esto se convierte en una implementación a ciegas.
|
|
11
10
|
|
|
12
11
|
El issue tracker debería estar descrito en `docs/agents/issue-tracker.md`. Si falta, pregunta al usuario dónde viven los tickets y cómo se cierra el trabajo (PR, issue, fichero).
|
|
13
12
|
|
package/skills/init/SKILL.md
CHANGED
|
@@ -165,11 +165,13 @@ means the user starts clean.
|
|
|
165
165
|
machine and flags overlap or skills with no footprint, so the project starts with the right set
|
|
166
166
|
rather than a pile. Summarize at their level.
|
|
167
167
|
4. **Tell them about the danger guard.** A `technical_level` of `non-technical` or `mixed` (and the
|
|
168
|
-
state before any profile exists) turns on a `PreToolUse` guard
|
|
169
|
-
`rm -rf`, `git push --force`, `git reset --hard`, `DROP`/`TRUNCATE`,
|
|
170
|
-
`WHERE`, `dd` to a device, `curl | bash
|
|
171
|
-
|
|
172
|
-
|
|
168
|
+
state before any profile exists) turns on a `PreToolUse` guard for irreversible commands —
|
|
169
|
+
`rm -rf`, `git push --force`, `git reset --hard`, `git branch -D`, `DROP`/`TRUNCATE`,
|
|
170
|
+
`DELETE`/`UPDATE` with no `WHERE`, `dd` to a device, `curl | bash`. For `non-technical` (or no
|
|
171
|
+
profile) Claude Code asks the user to confirm each one, saying what the command destroys; their
|
|
172
|
+
yes is the order that runs it. For `mixed` it only warns. A fully `technical` user is never
|
|
173
|
+
guarded. It turns off only if the user explicitly asks: `.rsc/.no-danger-guard`.
|
|
174
|
+
Mention it when you set a non-technical level, so a later confirmation is not a surprise.
|
|
173
175
|
|
|
174
176
|
### Phase 5 — HANDOFF
|
|
175
177
|
|
|
@@ -13,7 +13,7 @@ fix slow SQL, run zero-downtime migrations, and operate/secure the database. Too
|
|
|
13
13
|
example is runnable.
|
|
14
14
|
|
|
15
15
|
New, non-trivial feature with no approved spec + plan under `docs/wiki/sdd/`? Hand off to
|
|
16
|
-
the `to-spec` skill before writing feature code (
|
|
16
|
+
the `to-spec` skill before writing feature code (then `to-tickets`); build
|
|
17
17
|
straight from here only for a genuinely one-line, low-risk change.
|
|
18
18
|
|
|
19
19
|
Deep dives: [schema-and-indexing](references/schema-and-indexing.md) (types, constraints, every index
|
package/skills/suggest/SKILL.md
CHANGED
|
@@ -34,8 +34,8 @@ The shape of the chain:
|
|
|
34
34
|
when the effort will not fit one sitting.
|
|
35
35
|
- Once the shape is known, capture it as a spec, and give any call that would be costly to undo
|
|
36
36
|
its own ADR.
|
|
37
|
-
-
|
|
38
|
-
|
|
37
|
+
- Small work builds straight from the clarified ask; big work is split into tickets first and
|
|
38
|
+
built from them. A screen also routes through `flujo-interfaz`.
|
|
39
39
|
- Review the finished change against the repo's standards and against the spec before merging.
|
|
40
40
|
|
|
41
41
|
Two exceptions, and say out loud when you take one:
|
|
@@ -54,9 +54,9 @@ whole run — advance through the phases without re-asking.
|
|
|
54
54
|
If a step's skill is not installed, offer to add it (§2) before routing. `implement` always
|
|
55
55
|
pairs with test-driven development and a verification pass before anything is called done.
|
|
56
56
|
|
|
57
|
-
Exact skill names
|
|
58
|
-
`
|
|
59
|
-
`
|
|
57
|
+
Exact skill names, by size. Small: `superpowers:brainstorming` or `grill-with-docs` ->
|
|
58
|
+
`implement`. Big: `grill-with-docs` or `wayfinder` -> `to-spec` -> `to-tickets` ->
|
|
59
|
+
`implement-spec`. Both: `write-adr` for decisions, then `code-review` -> `pr`.
|
|
60
60
|
On Claude Code this rule also arrives as a per-turn hook; the brevity here is deduplication,
|
|
61
61
|
not relaxation.
|
|
62
62
|
|
|
@@ -71,13 +71,12 @@ name it and offer it. This runs mid-conversation, not only at project start.
|
|
|
71
71
|
1. `npx @damiangilgonzalez/harness catalog --available` lists every not-installed skill as
|
|
72
72
|
`id available short description`.
|
|
73
73
|
2. Pick the single best fit **by meaning**, the way you would match a request to a teammate's
|
|
74
|
-
expertise
|
|
75
|
-
|
|
76
|
-
and move on: a tangential suggestion is worse than none.
|
|
74
|
+
expertise, even when no keyword overlaps. If nothing genuinely fits, say so and move on: a
|
|
75
|
+
tangential suggestion is worse than none.
|
|
77
76
|
3. Ask once, plainly: "Para esto instalaría `<id>`, que aún no tienes. ¿La instalo? (sí/no)".
|
|
78
77
|
4. On yes, run `npx @damiangilgonzalez/harness add <id>`, then continue the original task.
|
|
79
78
|
|
|
80
|
-
|
|
79
|
+
One suggestion at a time,
|
|
81
80
|
and never to interrupt a flow with a nice-to-have. Never recommend something already installed
|
|
82
81
|
(`npx @damiangilgonzalez/harness list`).
|
|
83
82
|
|
|
@@ -95,9 +94,8 @@ Not covered → one line, skill or agent. Rules: `skill-scout`.
|
|
|
95
94
|
|
|
96
95
|
## 3. A harness that is broken fixes itself
|
|
97
96
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
nothing they recognise. When any of these is true, act on it **once** in the session:
|
|
97
|
+
Its owner rarely notices: the symptoms name nothing they know. When any of these
|
|
98
|
+
is true, act on it **once** in the session:
|
|
101
99
|
|
|
102
100
|
- `.rsc.json` exists and what it declares is not what is installed;
|
|
103
101
|
- the same hook seems to run several times;
|
|
@@ -117,7 +115,7 @@ a cause. Then:
|
|
|
117
115
|
teammate added, disarming a gate — **ask first**. A `git pull` never rewrites someone's
|
|
118
116
|
machine.
|
|
119
117
|
|
|
120
|
-
Offer once per session
|
|
118
|
+
Offer once per session; say nothing when the harness is healthy.
|
|
121
119
|
|
|
122
120
|
## 4. First contact
|
|
123
121
|
|
|
@@ -130,6 +128,12 @@ Before handling the first request of a session, check the workspace:
|
|
|
130
128
|
`.rsc/.no-harness`, confirm in one line, and never auto-start `init` in this repo again.
|
|
131
129
|
- Once the profile exists, this gate is inert. Never re-onboard.
|
|
132
130
|
|
|
131
|
+
## 5. «Recuerda esto»
|
|
132
|
+
|
|
133
|
+
A project or business fact goes to its topic page in `docs/wiki/` (link a new page from
|
|
134
|
+
`wiki/index.md`); say the path. Your own memory sits outside the project, unseen by person and
|
|
135
|
+
team: keep there only how this person likes you to work (or all, without `docs/`).
|
|
136
|
+
|
|
133
137
|
## Explain without assuming
|
|
134
138
|
|
|
135
139
|
Define a term at first use; never give a command, flag or path without saying what it does.
|
|
@@ -76,3 +76,9 @@ capability:
|
|
|
76
76
|
- "Names that one missing automation/connector skill"
|
|
77
77
|
- "Asks one short confirmation before installing and does not auto-install"
|
|
78
78
|
- "On confirmation, runs `npx @damiangilgonzalez/harness add <id>` for that one missing skill, then resumes the automation task"
|
|
79
|
+
|
|
80
|
+
- scenario: "In a workspace with `docs/wiki/`, the user says 'recuerda que toda la actividad, también la de Andorra, va por una sola sociedad, Nadsan 2006 S.L.'. Show where rsc-suggest puts it."
|
|
81
|
+
must_include:
|
|
82
|
+
- "Writes the fact into the `docs/wiki/` page of its topic (creating it and linking it from `wiki/index.md` if none exists), not only into the assistant's own memory under `~/.claude/projects/`"
|
|
83
|
+
- "Tells the person the path where it was saved"
|
|
84
|
+
- "Does not start the feature chain or ask a lane question for it"
|
package/skills/to-spec/SKILL.md
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
name: to-spec
|
|
3
3
|
description: "Turns the current conversation into a spec (PRD) and publishes it to the project's issue tracker — no interview, just a synthesis of what has already been discussed."
|
|
4
4
|
tags: ["spec", "prd", "issue-tracker"]
|
|
5
|
-
disable-model-invocation: true
|
|
6
5
|
profiles: [core, ui, full]
|
|
7
6
|
origin: damiangil
|
|
8
7
|
---
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
name: to-tickets
|
|
3
3
|
description: "Breaks down a plan, a spec, or the current conversation into a set of \\"tracer bullet\\" tickets (vertical slices), each declaring which tickets block it, and publishes them to the configured tracker — as local files (one per ticket) or as issues with native blocking links in a real tracker."
|
|
4
4
|
tags: ["tickets", "planning", "issue-tracker"]
|
|
5
|
-
disable-model-invocation: true
|
|
6
5
|
profiles: [core, ui, full]
|
|
7
6
|
origin: damiangil
|
|
8
7
|
---
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
name: wayfinder
|
|
3
3
|
description: "Plans a huge chunk of work — more than fits in a single agent session — as a shared map of decision tickets in the repo's issue tracker, and resolves them one at a time until the path to the destination is clear."
|
|
4
4
|
tags: ["planning", "issue-tracker", "large-scale-work"]
|
|
5
|
-
disable-model-invocation: true
|
|
6
5
|
profiles: [core, ui, full]
|
|
7
6
|
origin: damiangil
|
|
8
7
|
---
|
package/targets/_md-block.js
CHANGED
|
@@ -21,7 +21,7 @@ export function writeSkill(id, fromDir, toPath) {
|
|
|
21
21
|
export function wireHook(paths, sourceMd, policy = {}) {
|
|
22
22
|
const full = stripFrontmatter(readFileSync(sourceMd, 'utf8'));
|
|
23
23
|
const body = policy.codeHooks === false
|
|
24
|
-
? `# rsc-suggest — always-on operations layer\n\nRead \`docs/wiki/harness/user-profile.md\` before acting. If the user wants to build, add or change something,
|
|
24
|
+
? `# rsc-suggest — always-on operations layer\n\nRead \`docs/wiki/harness/user-profile.md\` before acting. If the user wants to build, add or change something, pick the chain by size and chain each step yourself. Small (one sitting, one ticket): clarify with \`superpowers:brainstorming\` (idea still vague) or \`grill-with-docs\` (a plan to harden), then build with \`implement\`. Big: \`grill-with-docs\` or \`wayfinder\` -> \`to-spec\` -> \`to-tickets\` -> \`implement-spec\`. Hard-to-reverse decisions go to \`write-adr\`; review with \`code-review\` and write the PR with \`pr\` before merging (si toca pantalla, \`flujo-interfaz\` dice que skill de diseno manda) — unless it is a one-line change, typo, or config bump, which skips the chain. Use \`orient\` to keep the user situated and \`suggest\` to offer a missing skill only when the current task needs it. Close with the configured orientation block.\n`
|
|
25
25
|
: full;
|
|
26
26
|
const block = `${MARK_START}\n${body}\n${MARK_END}`;
|
|
27
27
|
let doc = existsSync(paths.hookTarget) ? readFileSync(paths.hookTarget, 'utf8') : '';
|
package/targets/claude.js
CHANGED
|
@@ -175,7 +175,8 @@ export function wireHook(paths, sourceMd, policy = {}) {
|
|
|
175
175
|
|
|
176
176
|
if (policy.codeHooks !== false) {
|
|
177
177
|
// Ship guard: a PreToolUse(Bash) hook that DENIES switching to / merging the trunk
|
|
178
|
-
// while the current feature branch has
|
|
178
|
+
// while the current feature branch has unpushed commits (uncommitted changes only get a
|
|
179
|
+
// worktree hint: they may be the user's own) — forcing the
|
|
179
180
|
// commit → push → PR close (the `ship` skill). Materialized + node-run (Windows-safe),
|
|
180
181
|
// registered idempotently, fail-open, opt-out via .rsc/.no-ship-guard. Other
|
|
181
182
|
// (non-rsc) PreToolUse hooks are preserved.
|
|
@@ -43,7 +43,7 @@ const SEMVER = /^\d+\.\d+\.\d+(?:-[0-9a-z.-]+)?$/i;
|
|
|
43
43
|
* nothing checks is just drift with a head start.
|
|
44
44
|
*/
|
|
45
45
|
export const PROJECT_OPT_OUTS = [
|
|
46
|
-
'audit', 'claudemd-check', 'danger-guard', 'feature-gate', 'gitmoji', 'ship-guard', 'worktree-cleanup',
|
|
46
|
+
'audit', 'claudemd-check', 'danger-guard', 'feature-gate', 'gitmoji', 'knowledge-sync', 'ship-guard', 'trunk-guard', 'trunk-open', 'worktree-cleanup', 'worktree-isolation',
|
|
47
47
|
];
|
|
48
48
|
|
|
49
49
|
/**
|