orchestrix-skills 0.7.0 → 0.8.0

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/bin/install.js CHANGED
@@ -2,13 +2,18 @@
2
2
  // orchestrix-skills installer — zero dependencies.
3
3
  // Free path: copy skills into the runtime's skills dir + scaffold knowledge/.
4
4
  // No MCP, no license. Premium (hosted orchestrator / KB / 建造中心) is a separate opt-in.
5
- import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
5
+ import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
6
6
  import { dirname, join } from "node:path";
7
7
  import { fileURLToPath } from "node:url";
8
8
 
9
9
  const PKG = join(dirname(fileURLToPath(import.meta.url)), "..");
10
10
  const MANAGED_START = "<!-- orchestrix:start -->";
11
11
  const MANAGED_END = "<!-- orchestrix:end -->";
12
+ // Written into the skills dir after every successful install. Two consumers:
13
+ // this installer (prunes skills it placed that the package no longer ships) and
14
+ // hosts that auto-upgrade projects (compare `version` against the npm dist-tag
15
+ // to decide whether to reinstall — no second version constant to maintain).
16
+ const STAMP = ".orchestrix-skills.json";
12
17
 
13
18
  // Where each runtime auto-loads skills from (relative to the target project).
14
19
  function adapter(name) {
@@ -54,9 +59,22 @@ function transformSkill(source, runtime) {
54
59
  );
55
60
  }
56
61
 
62
+ function packageVersion() {
63
+ return JSON.parse(readFileSync(join(PKG, "package.json"), "utf8")).version;
64
+ }
65
+
66
+ function readStamp(target) {
67
+ try {
68
+ return JSON.parse(readFileSync(join(target, STAMP), "utf8"));
69
+ } catch {
70
+ return null; // absent, or written by a version that predates stamping
71
+ }
72
+ }
73
+
57
74
  function installSkills(dir, runtimeName, runtime) {
58
75
  const target = join(dir, runtime.skillsDir);
59
76
  mkdirSync(target, { recursive: true });
77
+ const previous = readStamp(target);
60
78
  const entries = readdirSync(join(PKG, "skills"), { withFileTypes: true });
61
79
  for (const entry of entries) {
62
80
  const source = join(PKG, "skills", entry.name);
@@ -74,7 +92,26 @@ function installSkills(dir, runtimeName, runtime) {
74
92
  }
75
93
  }
76
94
  }
77
- return entries.filter((entry) => entry.isDirectory()).length;
95
+
96
+ const names = entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name);
97
+ // Retire skills a PREVIOUS install of this package placed that it no longer
98
+ // ships. Only names recorded in our own stamp are candidates, so a skill the
99
+ // project or its host installed alongside ours is never touched.
100
+ let pruned = 0;
101
+ for (const name of previous?.skills ?? []) {
102
+ if (names.includes(name)) continue;
103
+ const stale = join(target, name);
104
+ if (!isDirectory(stale)) continue;
105
+ rmSync(stale, { recursive: true, force: true });
106
+ pruned += 1;
107
+ }
108
+ // Stamp LAST: a crash mid-copy leaves the older stamp in place, so the next
109
+ // run still sees a mismatch and reinstalls rather than declaring itself current.
110
+ writeFileSync(
111
+ join(target, STAMP),
112
+ `${JSON.stringify({ version: packageVersion(), ide: runtimeName, skills: names }, null, 2)}\n`,
113
+ );
114
+ return { count: names.length, pruned };
78
115
  }
79
116
 
80
117
  function installRuntimeGuidance(dir, runtimeName) {
@@ -142,8 +179,9 @@ function install() {
142
179
  }
143
180
 
144
181
  // 1. Skills (capabilities) — always refreshed.
145
- const count = installSkills(dir, ide, runtime);
146
- console.log(`✓ ${count} skills → ${runtime.skillsDir}/`);
182
+ const { count, pruned } = installSkills(dir, ide, runtime);
183
+ console.log(`✓ ${count} skills → ${runtime.skillsDir}/ (v${packageVersion()})`);
184
+ if (pruned > 0) console.log(`✓ ${pruned} retired skill(s) removed`);
147
185
  installRuntimeGuidance(dir, ide);
148
186
 
149
187
  // 2. Knowledge (the brain) — scaffold only if absent; never clobber the user's brain.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "orchestrix-skills",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "Capability-first AI development skill graph — Anthropic-native skills that run in any agent runtime.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -4,7 +4,7 @@ description: Use when a goal must be delivered end-to-end by composing skills, w
4
4
  license: MIT
5
5
  allowed-tools: [Read, Write, Edit, Bash, Grep, Glob, Task]
6
6
  metadata:
7
- version: 5
7
+ version: 6
8
8
  requires:
9
9
  capabilities: [filesystem.read, filesystem.write, shell.execute, "agent.spawn?"]
10
10
  contract:
@@ -36,9 +36,10 @@ no step above intent.
36
36
  1. **Bind intent.** Read the human's goal and constraints. This is the only
37
37
  place intent enters. Then run the first-run preflight (below) before any
38
38
  wiring.
39
- 2. **Select.** Read the skill registry. Pick skills by their `description`
40
- (when-to-use). Load a skill's full `contract` only when it is a candidate —
41
- never load every contract at once.
39
+ 2. **Select.** Read the skill registry (the `description:` line of every
40
+ SKILL.md in the runtime's skills directory). Pick skills by that
41
+ when-to-use description. Load a skill's full `contract` only when it is a
42
+ candidate — never load every contract at once.
42
43
  3. **Wire (emergent, not hardcoded).** Build the path by matching one skill's
43
44
  `outputs` to the next skill's `inputs`. Skills do not know each other; only
44
45
  you do. Do not assume a fixed pipeline — wire what this intent needs.
@@ -68,8 +69,12 @@ no step above intent.
68
69
  6. **Accept (gate).** Apply the rule below. Then continue — do not pause to ask
69
70
  "should I keep going?" mid-run.
70
71
  7. **Repeat** 3–6 until the intent is fulfilled.
71
- 8. **Final acceptance.** Present the batched deferred accepts and a final review
72
- to the human, once. Apply corrections (see Metabolism), then deliver.
72
+ 8. **Final acceptance.** FIRST re-read the intent from the `run_start` ledger
73
+ line and check the assembled result against IT every step passing its own
74
+ verify does not prove the composition serves the intent (steps can each be
75
+ right while the whole drifts). Then present the batched deferred accepts and
76
+ a final review to the human, once. Apply corrections (see Metabolism), then
77
+ deliver.
73
78
 
74
79
  ## Namespace resolution (`core-config.yaml`)
75
80
 
@@ -114,6 +119,27 @@ resolved paths):
114
119
 
115
120
  Both checks are per-run and idempotent: a populated brain makes them no-ops.
116
121
 
122
+ ## Hard wiring rules (what emergence cannot reach)
123
+
124
+ Output→input matching wires most of the graph. Two skills it structurally
125
+ CANNOT reach — wire these by rule, not by match:
126
+
127
+ 1. **`smoke-test` is the acceptance floor for runnable apps.** Nothing in the
128
+ graph outputs its `flows` or `run_instructions`, so no output→input match
129
+ will ever select it. If the deliverable is a runnable app or service and
130
+ this run changed it, wire `smoke-test` before final acceptance and derive
131
+ its inputs yourself: `flows` from the story's acceptance criteria (or from
132
+ the intent, when there is no story), `run_instructions` from `registry/app`
133
+ (or the project's own manifest). `run-tests` proves functions; `smoke-test`
134
+ proves the product — green unit tests are not this evidence. A `failed` or
135
+ `untested` verdict is a real result: carry it into final acceptance
136
+ verbatim, never round it up to passed.
137
+ 2. **`design-system` comes before `design-ui`.** `design-ui` READS
138
+ `taste/design-system` — it never produces it. If UI work is wired and the
139
+ resolved `taste/design-system` namespace is empty, wire `design-system`
140
+ first; otherwise `design-ui` dresses a project that has a brand in generic
141
+ defaults.
142
+
117
143
  ## Accept gate
118
144
 
119
145
  | Skill's `accept.timing` | Skill's `authority` | Action |
@@ -141,8 +167,9 @@ aimed at a symptom re-rolls the dice.
141
167
  STOP the run — do not burn a 4th attempt. Write a `gate` event to the ledger
142
168
  (`{"e":"gate","kind":"rework_exhausted","question":"step <n> (<skill>) failed 3
143
169
  attempts: <one-line why>"}`), summarize the three failures for the human, and
144
- report AWAIT. A step that cannot pass its own verify after three tries needs a
145
- human decision (wrong approach, wrong spec, or wrong verify), not more tokens.
170
+ stop for their decision. A step that cannot pass its own verify after three
171
+ tries needs a human (wrong approach, wrong spec, or wrong verify), not more
172
+ tokens.
146
173
 
147
174
  ## Metabolism — governed writeback
148
175
 
@@ -181,13 +208,18 @@ platform renders it as live progress). It is append-only JSONL: one JSON event
181
208
  per line, appended with `Bash` (`echo '<json>' >> .orchestrate/ledger.jsonl`).
182
209
  Never rewrite or delete lines. Timestamps: `date -u +%FT%TZ`.
183
210
 
211
+ **Quoting hazard:** the single-quoted `echo` breaks on `'` inside the JSON —
212
+ and a mangled line corrupts the run's only durable memory. Keep every free-text
213
+ field (`intent`, `question`, `title`) to one line with no single quotes:
214
+ rephrase (`don't` → `do not`) before writing, never fight the shell escaping.
215
+
184
216
  Events and when to write them:
185
217
 
186
218
  | Event | When | Shape |
187
219
  | ----- | ---- | ----- |
188
220
  | `run_start` | right after binding intent | `{"e":"run_start","run":"r-<yyyymmdd>-<slug>","intent":"...","ts":"..."}` |
189
221
  | `plan` | after wiring the graph, and EVERY time the graph changes | `{"e":"plan","run":"...","steps":[{"n":1,"skill":"research","title":"..."}, …]}` — full current plan; latest `plan` line wins; steps may be added, never removed |
190
- | `step` | immediately BEFORE each dispatch, and again after its verify | `{"e":"step","run":"...","n":3,"skill":"implement","status":"dispatched\|done\|failed","attempt":1,"evidence":"<file or one-line result>","ts":"..."}` — rework = same `n`, next `attempt` |
222
+ | `step` | immediately BEFORE each dispatch, and again after its verify | `{"e":"step","run":"...","n":3,"skill":"implement","status":"dispatched\|done\|failed\|skipped","attempt":1,"evidence":"<file or one-line result>","ts":"..."}` — rework = same `n`, next `attempt`; a step a replan made obsolete gets `skipped` with the reason in `evidence` (plan lines are never removed, so this is how an obsolete step closes) |
191
223
  | `gate` | when stopping at a human gate | `{"e":"gate","run":"...","kind":"inline_accept","question":"...","ts":"..."}` |
192
224
  | `run_end` | at delivery or abandonment | `{"e":"run_end","run":"...","result":"delivered\|paused\|abandoned","ts":"..."}` |
193
225
 
@@ -196,6 +228,24 @@ step is required and should be the step's verify log path
196
228
  (`.orchestrate/verify/step-<n>-attempt-<k>.log`); a `done` with no evidence is
197
229
  a false claim.
198
230
 
231
+ ## Resume — cold re-entry (deterministic, not from memory)
232
+
233
+ Whenever you enter with an existing ledger — after compaction, an interrupted
234
+ session, or a wake-up — do NOT continue from what you remember. Replay:
235
+
236
+ 1. Read `.orchestrate/ledger.jsonl`. The active run is the last `run_start`
237
+ with no matching `run_end`. Its `intent` line — not your recollection — is
238
+ what you are delivering. No active run → this is a fresh start.
239
+ 2. Rebuild state from events alone: the latest `plan` wins; `done` and
240
+ `skipped` steps are closed; prior `attempt` values count toward each step's
241
+ cap of 3.
242
+ 3. **A dangling `dispatched`** (no `done`/`failed`/`skipped` after it) means
243
+ that attempt was cut off mid-flight. Trust it in NEITHER direction: run that
244
+ step's verify command now. Pass → append its `done` with the evidence.
245
+ Fail → re-dispatch as the next attempt.
246
+ 4. Continue the loop from the first open step. If the run was stopped at a
247
+ `gate`, re-ask that gate's question — never assume it was answered.
248
+
199
249
  ## Context discipline (stay lean)
200
250
 
201
251
  - **Files, not paste.** Move artifacts between steps as files. Never paste a
@@ -211,6 +261,8 @@ a false claim.
211
261
  - Hardcoding a fixed skill order instead of wiring outputs→inputs
212
262
  - Pasting a step's full output into your context instead of handing a file
213
263
  - Re-dispatching a step the ledger already marks done
264
+ - Resuming from memory instead of replaying the ledger — or trusting a
265
+ dangling `dispatched` in either direction without running its verify
214
266
  - Dispatching a step without first writing its `dispatched` ledger line
215
267
  - Ending a run without a `run_end` ledger line
216
268
  - Marking a step done on the subagent's say-so, without your own verify command
@@ -219,4 +271,7 @@ a false claim.
219
271
  - Appending to `taste/*` without reading it first (duplicate/contradiction risk)
220
272
  - Dispatching a design/build skill in an existing codebase while `registry/*`
221
273
  is empty (first-run preflight skipped)
274
+ - Delivering a runnable app this run changed with no `smoke-test` verdicts
275
+ (unit tests are not that evidence)
276
+ - Dispatching `design-ui` while the resolved `taste/design-system` is empty
222
277
  - Marking the run complete without every step's `verify` evidence