@lifeaitools/rdc-skills 0.35.23 → 0.35.24

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.
@@ -0,0 +1,183 @@
1
+ # Work Contract — truth at the start, Stop is the checklist
2
+
3
+ > The one home for how an `rdc:*` skill declares work, resolves its target, and
4
+ > proves it is finished. `rdc:open`, `rdc:fixit`, `rdc:build`, `rdc:plan`,
5
+ > `rdc:overnight` and `rdc:review` defer to this file rather than restating it.
6
+ > Runtime: `lifeai-env` ≥ 0.8.257 (`$LIFEAI_ENV/bin/rdc-work.mjs`), same command
7
+ > on Claude Code and Codex. When a gate prints an `rdc-work` line, run **that
8
+ > line**: it names the installed copy and carries `--session <id>`, which matters
9
+ > in a shell that holds both a Claude and a Codex session id.
10
+
11
+ ## Why
12
+
13
+ Every gate this fleet had judged work at the end, by guessing — a regex over the
14
+ final message, a transcript scan for a todo list. A guess at the end cannot tell
15
+ half-built from finished. Operator, 2026-09-14/16:
16
+
17
+ - "the best truth is to start with a plan that has to be finished"
18
+ - "we should have a start gate is more important than the stop gate"
19
+ - "The agent can decide if the work is worth A To Do List or a work package"
20
+ - "hard undeniable proof of work"
21
+ - "move truth to the start so stop is a checklist"
22
+
23
+ ## The contract
24
+
25
+ Before a session's first change — a file write in a git work tree, a `git commit`,
26
+ a `git push` — it declares:
27
+
28
+ | field | what it is |
29
+ |---|---|
30
+ | `goal` | one sentence: what done means |
31
+ | `rows` | each a step **and the command that proves it**: `"<step> :: <command that exits 0 when the step is true>"` |
32
+ | `type` | the kind of work; it picks the tier |
33
+
34
+ ```bash
35
+ node "$LIFEAI_ENV/bin/rdc-work.mjs" start --type fixit \
36
+ --goal "checkout page renders prices from the API again" \
37
+ --row "unit tests pass :: pnpm --filter @lifeai/shop test" \
38
+ --row "page shows a real price :: curl -sf https://dev.shop.example/checkout | grep -q 'data-price'" \
39
+ --row "landed on develop :: git fetch -q origin && git merge-base --is-ancestor HEAD origin/develop"
40
+ ```
41
+
42
+ A row passes only when its command ran and exited 0. **Nothing ticks a row by
43
+ saying so** — there is no free-text evidence path.
44
+
45
+ Without a contract the start gate (`work-contract-required`) refuses the change
46
+ and prints the exact command, including this session's id. Plans, reports and
47
+ other documents under `.rdc/`, engine memory and plan files, and anything outside
48
+ a git work tree need no contract; code does, wherever its checkout sits.
49
+
50
+ ### A proof observes, and it can fail
51
+
52
+ A proof runs through `rdc-work`'s own process, outside the hooks that guard a
53
+ tool call, so `rdc-work` applies both checks itself — at `start`/`add` and again at
54
+ `verify`:
55
+
56
+ - **The proof policy.** A proof may not change anything: no commit, push, merge,
57
+ rebase, reset, checkout, stash, tag or branch change; no publish, land, deploy,
58
+ migration, PM2/Docker/Kubernetes lifecycle; no `rm`/`mv`/`cp`/`touch`/`mkdir`,
59
+ `sed -i`, redirection into a file, or write request (`curl -X POST`, `-d`). The
60
+ command is read the way the shell runs it, so wrapping an action does not hide it —
61
+ `sh -c '…'`, `cmd /c …`, `npx …`, `node -e "…writeFileSync…"` are judged by what they
62
+ carry. And it must be able to fail: `true`, `exit 0`, a bare `echo`, `… || true`,
63
+ `test 1` are refused — and so is `x | tail`, because a pipeline exits with its LAST
64
+ command (prove with `x` itself, or `x | grep -q <evidence>`).
65
+ - **The shared guard rules** — the same ones a Bash call meets. A proof they refuse
66
+ is refused (`( cd dir && cmd )`, not `cd dir && cmd`).
67
+
68
+ A refused proof is recorded as exit 126 and **never runs**. Prove an action by
69
+ observing its result: a push with `git merge-base --is-ancestor HEAD origin/<branch>`,
70
+ a deploy with a read-only probe, a file with `test -f`.
71
+
72
+ ## Tier — the agent picks
73
+
74
+ | `--type` | tier | also |
75
+ |---|---|---|
76
+ | `maintenance`, `hotfix`, `fixit`, `edit` | `todo` | rows only |
77
+ | `build`, `refactor`, `overnight` | `work-item` | `--work-item <uuid>` required |
78
+
79
+ Whatever the tier, a work item this session has **claimed** holds Stop until its
80
+ database Definition of Done closes — choosing `todo` does not drop a claimed item's DoD.
81
+
82
+ A todo contract that grows past ~5 changed files is reported at Stop ("usually
83
+ wants a work item") but never blocked on — the tier is the agent's call. Upgrade
84
+ with `rdc-work upgrade --work-item <uuid>`.
85
+
86
+ ## Rows — proportional, not a quota
87
+
88
+ One row per **deliverable**, each with one proof. Decompose until every declared
89
+ surface (screen / api / db / tool) and every handoff has a row that can pass or fail
90
+ on its own. There is **no minimum row count**: a numeric floor produced 91 checks
91
+ before a runnable increment (Codex, building CDE Layer 0). Coverage is the rule;
92
+ count is not.
93
+
94
+ Coarse rows are still rejected, because they cannot fail: "works", "verified",
95
+ "integration complete", "tests pass" with no command.
96
+
97
+ ## Proving, and staleness
98
+
99
+ ```bash
100
+ node "$LIFEAI_ENV/bin/rdc-work.mjs" verify t2 --session <id> # one row
101
+ node "$LIFEAI_ENV/bin/rdc-work.mjs" verify --all --session <id> # every row, against the code as it stands now
102
+ node "$LIFEAI_ENV/bin/rdc-work.mjs" drop t3 --reason "no dev target exists for this package" --session <id>
103
+ node "$LIFEAI_ENV/bin/rdc-work.mjs" status --session <id> # the checklist Stop will judge
104
+ node "$LIFEAI_ENV/bin/rdc-work.mjs" check --session <id> # exit 0 when Stop would pass
105
+ ```
106
+
107
+ `--session` is optional when the shell carries exactly one engine's session id;
108
+ every verb prints the id it resolved and where it came from. `--no-db` skips the
109
+ work-item DoD lookup for an offline check — Stop never skips it.
110
+
111
+ A proof goes **stale** when the session edits after it, or when the content it ran
112
+ against changes by any route — a shell edit, a formatter, a rebase, a pull, a new
113
+ file.
114
+ Committing exactly the proved content does not make it stale. Finish every piece of
115
+ work with `verify --all`. A dropped row is resolved, not passed, and stays visible
116
+ with its reason. Every row prints its proof command beside it, so a weak proof is as
117
+ visible as its claim.
118
+
119
+ ## Stop
120
+
121
+ For a session **holding a contract**, Stop is `rdc-work check`: every row proved
122
+ against the current content or dropped with a reason; the target's changes committed —
123
+ including new files this session wrote; the changes it wrote in **any other
124
+ repository** committed too (a second repo, a subagent's worktree); and the database DoD
125
+ closed for every work item it claimed or declared. Dropping every row after making
126
+ changes is not a pass — a drop explains, it does not prove. The block message is the
127
+ checklist, with runnable `--session` commands. A database outage is reported, not held
128
+ against the work.
129
+
130
+ A session with **no contract** — one that only read, planned or answered — still
131
+ gets the evidence checks: tracked changes it left uncommitted, and a claimed work
132
+ item's open DoD. How its final message is worded is never judged.
133
+
134
+ Enforcement is bounded: a Stop held identically three times, or six times in a row,
135
+ releases, and the unproved rows stay in the contract and in the compaction snapshot.
136
+ A defect in the gate itself — an unreadable contract, an error — reports and never holds.
137
+
138
+ ## Target resolution — never assume regen-root
139
+
140
+ `start` resolves the target from `projects.json` (generated from Supabase
141
+ `repo_registry`) and prints it:
142
+
143
+ ```
144
+ target: C:/Dev/lifeai-env.wt/x · integration main · ship: node machines/land.mjs
145
+ ```
146
+
147
+ Skill text written for regen-root names `develop`, `.rdc/guides`, `scripts/land.mjs`
148
+ and the CodeFlow phase orchestrator. **Those are regen-root's facts, not universal
149
+ ones.** Wherever a skill names them:
150
+
151
+ | skill says | use instead |
152
+ |---|---|
153
+ | `develop` / `origin/develop` | the contract's **integration** branch (`rdc-work status`). regen-root and rdc-harness: `develop`. lifeai-env, clauth, rdc-cde: `main`. rdc-skills: `master`. |
154
+ | `{PROJECT_ROOT}/.rdc/guides/<x>.md` | the contract's `guides_dir` if the target has one; otherwise the guides shipped with rdc-skills. **Never** a cwd-relative `.rdc/guides` from a different repository — that imports one repo's rules into another. |
155
+ | `node scripts/land.mjs` | the contract's **ship** route |
156
+ | `runOrchestrator()` with the phase manifest | only when the target contains `corpus/_shared/build/phase-manifest.json` (today: regen-root). Elsewhere the orchestrator does not apply: resolve waves from work-item dependencies and say so in one line. **An absent orchestrator is not a blocker.** |
157
+ | an undeclared target (`NOT in projects.json`) | work proceeds on rows; register the repository in `repo_registry` before relying on a ship route |
158
+
159
+ ## Dispatching writers — engine-neutral
160
+
161
+ The skill's `Agent(…, isolation: "worktree", max_turns: 70)` is Claude Code's
162
+ interface. On an engine without it:
163
+
164
+ 1. Create one worktree per writer from the **integration** branch:
165
+ `git fetch origin <integration> && git worktree add <pool>/<name> -b <branch> origin/<integration>`
166
+ (`<pool>` is the contract's `worktree_pool`).
167
+ 2. Start the writer with that worktree as its working directory.
168
+ 3. The writer is a subagent of this session only if the engine says so; a separately
169
+ launched session needs its own contract.
170
+
171
+ Claude subagents share the parent's session id, so they inherit the parent's
172
+ contract; a SubagentStop is never held to the parent's whole checklist.
173
+
174
+ ## Rollout switch
175
+
176
+ `~/.rdc/work-contract-mode`: `enforce` (default) · `shadow` (log would-blocks only)
177
+ · `off` (previous behaviour). A file, not an environment variable, so an agent
178
+ cannot flip it for itself — and `work-contract-tamper` refuses any agent tool call
179
+ that writes the mode file, a contract, or Stop's breaker state. The operator
180
+ switches the mode from their own terminal.
181
+
182
+ A worker launched by the Codex Development Environment (CDE) is admitted by CDE's
183
+ own manager and validator; the start gate stands down for it.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lifeaitools/rdc-skills",
3
- "version": "0.35.23",
3
+ "version": "0.35.24",
4
4
  "description": "RDC typed-agent dispatch skill suite for Claude Code - plan, build, review, overnight builds",
5
5
  "keywords": [
6
6
  "claude-code",
@@ -0,0 +1,49 @@
1
+ // guide-content-rules.mjs — the one home for the guide-content validator's rules.
2
+ //
3
+ // Imported by scripts/self-test.mjs (the validator) and scripts/test-guide-validator.mjs
4
+ // (its test). Both used to carry their own copy of these constants, because
5
+ // self-test.mjs runs on import and exports nothing. The copies drifted TOGETHER,
6
+ // so the test kept passing while the validator was wrong: the corrections-table
7
+ // exemption named `@regen/brand-studio`, the package scope was renamed to
8
+ // `@lifeai`, and `.claude/rules/naming-corrections.md` — the table whose whole
9
+ // purpose is to say that name is WRONG — failed every strict run as a "positive
10
+ // instruction" to use it (found 2026-09-16, failing on unmodified master too).
11
+
12
+ /** Terms that must NOT appear in guide/rule files as positive instructions. */
13
+ export const GUIDE_BANNED_TERMS = Object.freeze([
14
+ "@masonator/coolify-mcp",
15
+ "@masonator",
16
+ "coolify-mcp",
17
+ "@regen/brand-studio",
18
+ "brand-studio",
19
+ ]);
20
+
21
+ /**
22
+ * A line matching one of these is an explicit "don't use" statement, not an
23
+ * instruction, and a banned term on it is not flagged.
24
+ */
25
+ export const GUIDE_NEGATION_PATTERNS = Object.freeze([
26
+ /\bdo not\b/i,
27
+ /\bnever\b/i,
28
+ /\bno such\b/i,
29
+ /\bdoes not exist\b/i,
30
+ /\bbanned\b/i,
31
+ /\bnot reference\b/i,
32
+ /\bnot use\b/i,
33
+ /\bavoid\b/i,
34
+ /\bremoved\b/i,
35
+ /\bdeprecated\b/i,
36
+ // The header row of a WRONG → CORRECT table.
37
+ /^\|[^|]*WRONG[^|]*\|/i,
38
+ // A data row of a corrections table: the banned term sits in the FIRST cell and
39
+ // the next cell is the bold correction. STRUCTURAL on purpose — any scope or
40
+ // prefix before the term (`@regen/`, `@lifeai/`, the next rename) still matches.
41
+ // The previous form listed exact spellings, which is how one scope rename turned
42
+ // a correct row into a strict-mode failure.
43
+ /^\|\s*[^|]*?(?:brand-studio|Brand Studio|@masonator|coolify-mcp)[^|]*\|\s*\*\*/,
44
+ ]);
45
+
46
+ /** True when a banned term on this line is a warning or a correction, not an instruction. */
47
+ export function isNegatedBannedLine(line) {
48
+ return GUIDE_NEGATION_PATTERNS.some((re) => re.test(line));
49
+ }