specrails-core 4.8.2 → 4.9.1
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/specrails-core.mjs +5 -1
- package/dist/installer/cli.js +46 -6
- package/dist/installer/cli.js.map +1 -1
- package/dist/installer/commands/doctor.js +14 -5
- package/dist/installer/commands/doctor.js.map +1 -1
- package/dist/installer/commands/framework.js +134 -0
- package/dist/installer/commands/framework.js.map +1 -0
- package/dist/installer/commands/init.js +107 -32
- package/dist/installer/commands/init.js.map +1 -1
- package/dist/installer/commands/update.js +75 -35
- package/dist/installer/commands/update.js.map +1 -1
- package/dist/installer/phases/scaffold.js +493 -73
- package/dist/installer/phases/scaffold.js.map +1 -1
- package/dist/installer/util/fs.js +143 -1
- package/dist/installer/util/fs.js.map +1 -1
- package/dist/installer/util/registry.js +339 -0
- package/dist/installer/util/registry.js.map +1 -0
- package/package.json +2 -1
- package/pinned-versions.json +1 -1
- package/templates/agents/sr-architect.md +14 -10
- package/templates/agents/sr-backend-developer.md +4 -2
- package/templates/agents/sr-developer.md +20 -8
- package/templates/agents/sr-frontend-developer.md +4 -2
- package/templates/agents/sr-reviewer.md +10 -6
- package/templates/codex-skills/implement/SKILL.md +19 -10
- package/templates/codex-skills/rails/sr-architect/SKILL.md +17 -8
- package/templates/codex-skills/rails/sr-backend-developer/SKILL.md +4 -1
- package/templates/codex-skills/rails/sr-developer/SKILL.md +13 -4
- package/templates/codex-skills/rails/sr-doc-sync/SKILL.md +3 -2
- package/templates/codex-skills/rails/sr-frontend-developer/SKILL.md +4 -1
- package/templates/codex-skills/rails/sr-product-manager/SKILL.md +9 -7
- package/templates/codex-skills/rails/sr-reviewer/SKILL.md +13 -7
- package/templates/codex-skills/retry/SKILL.md +10 -5
- package/templates/commands/specrails/implement.md +41 -23
- package/templates/commands/specrails/retry.md +3 -1
- package/templates/gemini-commands/implement.toml +10 -6
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared artifact registry — the contract that lets specrails-core place,
|
|
3
|
+
* and specrails-desktop read, a repo's relocated artifacts OUTSIDE the repo,
|
|
4
|
+
* under `$HOME/.specrails/projects/<slug>/workspace`.
|
|
5
|
+
*
|
|
6
|
+
* Single source of truth: `$HOME/.specrails/registry.json`, an inspectable,
|
|
7
|
+
* schema-versioned JSON file keyed by the **canonical realpath of the repo**.
|
|
8
|
+
* specrails-desktop is the primary writer (a projection of its `desktop.sqlite`);
|
|
9
|
+
* specrails-core reads it and, when run standalone, allocates its own entry.
|
|
10
|
+
*
|
|
11
|
+
* Design contract: `docs/internals/global-artifacts-alignment-contract.md`
|
|
12
|
+
* (in the specrails-desktop repo). The slug algorithm, canonical-key rule,
|
|
13
|
+
* atomic-write rule and lock protocol here MUST stay byte-identical to the
|
|
14
|
+
* desktop-side implementation — the correctness of the cross-tool contract
|
|
15
|
+
* depends on both tools resolving the same repo to the same paths.
|
|
16
|
+
*
|
|
17
|
+
* Everything in this module is synchronous to fit the installer's sync flow.
|
|
18
|
+
*/
|
|
19
|
+
import { closeSync, existsSync, fsyncSync, mkdirSync, openSync, readFileSync, realpathSync, renameSync, statSync, unlinkSync, writeFileSync, } from 'node:fs';
|
|
20
|
+
import os from 'node:os';
|
|
21
|
+
import path from 'node:path';
|
|
22
|
+
/** Registry schema version. A reader that sees a higher value MUST treat all
|
|
23
|
+
* entries as absent (legacy fallback), never mis-parse. */
|
|
24
|
+
export const REGISTRY_SCHEMA_VERSION = 1;
|
|
25
|
+
/**
|
|
26
|
+
* Slug derivation — byte-identical to specrails-desktop's `slugify`
|
|
27
|
+
* (`server/desktop-router.ts`). Do not "improve" it; parity is the contract.
|
|
28
|
+
*/
|
|
29
|
+
export function slugify(name) {
|
|
30
|
+
return name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
|
|
31
|
+
}
|
|
32
|
+
/** `$HOME` for the registry, overridable for tests. */
|
|
33
|
+
export function resolveHome(home) {
|
|
34
|
+
return home ?? process.env.SPECRAILS_REGISTRY_HOME ?? os.homedir();
|
|
35
|
+
}
|
|
36
|
+
/** Absolute path to `registry.json`. */
|
|
37
|
+
export function registryPath(home) {
|
|
38
|
+
return path.join(resolveHome(home), '.specrails', 'registry.json');
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Absolute path to the versioned framework store
|
|
42
|
+
* (`<home>/.specrails/framework`). The bundled-framework split materializes the
|
|
43
|
+
* provider-invariant subtree to `<frameworkDir>/<version>/<providerDir>/` once
|
|
44
|
+
* and every workspace symlinks `<frameworkDir>/current/<providerDir>/...`. Shares
|
|
45
|
+
* the SAME home as the registry so framework, registry, and workspaces co-locate
|
|
46
|
+
* (and tests pinning `SPECRAILS_REGISTRY_HOME` redirect all three together).
|
|
47
|
+
*/
|
|
48
|
+
export function frameworkRoot(home) {
|
|
49
|
+
return path.join(resolveHome(home), '.specrails', 'framework');
|
|
50
|
+
}
|
|
51
|
+
/** Absolute path to the advisory lock file. */
|
|
52
|
+
export function lockPath(home) {
|
|
53
|
+
return registryPath(home) + '.lock';
|
|
54
|
+
}
|
|
55
|
+
/** `fs.realpathSync` that falls back to the resolved-but-unreal path on error
|
|
56
|
+
* (the path may not exist yet, or be on a volume that rejects realpath). */
|
|
57
|
+
export function realpathSafe(abs) {
|
|
58
|
+
try {
|
|
59
|
+
return realpathSync(abs);
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return abs;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/** Case-fold the key on case-insensitive platforms (macOS, Windows) so two
|
|
66
|
+
* spellings of the same path map to one entry. The stored `repoPath` keeps
|
|
67
|
+
* its canonical case; only the index key is folded. */
|
|
68
|
+
export function normalizeKey(canon) {
|
|
69
|
+
if (process.platform === 'darwin' || process.platform === 'win32') {
|
|
70
|
+
return canon.toLowerCase();
|
|
71
|
+
}
|
|
72
|
+
return canon;
|
|
73
|
+
}
|
|
74
|
+
/** Canonical repo path: resolve to absolute, then realpath (collapses symlinks). */
|
|
75
|
+
export function canonicalizeRepoPath(repoPathInput) {
|
|
76
|
+
return realpathSafe(path.resolve(repoPathInput));
|
|
77
|
+
}
|
|
78
|
+
/** The per-project sub-path layout under a workspace dir. Single source of the
|
|
79
|
+
* layout so writer and (allocation) reader never disagree. */
|
|
80
|
+
export function workspaceLayout(home, slug, canon) {
|
|
81
|
+
const workspaceDir = path.join(resolveHome(home), '.specrails', 'projects', slug, 'workspace');
|
|
82
|
+
const specrailsDir = path.join(workspaceDir, '.specrails');
|
|
83
|
+
return {
|
|
84
|
+
repoPath: canon,
|
|
85
|
+
slug,
|
|
86
|
+
workspaceDir,
|
|
87
|
+
artifactRoot: workspaceDir,
|
|
88
|
+
codeRoot: canon,
|
|
89
|
+
stateDir: path.join(workspaceDir, '.claude'),
|
|
90
|
+
ticketsPath: path.join(specrailsDir, 'local-tickets.json'),
|
|
91
|
+
backlogConfigPath: path.join(specrailsDir, 'backlog-config.json'),
|
|
92
|
+
profilesDir: path.join(specrailsDir, 'profiles'),
|
|
93
|
+
pluginsStateDir: path.join(specrailsDir, 'plugins'),
|
|
94
|
+
fileSummariesDir: path.join(specrailsDir, 'file-summaries'),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
/** The in-repo layout, used when there is no registry entry (legacy fallback). */
|
|
98
|
+
export function legacyResolution(canon) {
|
|
99
|
+
const specrailsDir = path.join(canon, '.specrails');
|
|
100
|
+
return {
|
|
101
|
+
key: normalizeKey(canon),
|
|
102
|
+
repoPath: canon,
|
|
103
|
+
slug: '',
|
|
104
|
+
workspaceDir: canon,
|
|
105
|
+
artifactRoot: canon,
|
|
106
|
+
codeRoot: canon,
|
|
107
|
+
stateDir: path.join(canon, '.claude'),
|
|
108
|
+
ticketsPath: path.join(specrailsDir, 'local-tickets.json'),
|
|
109
|
+
backlogConfigPath: path.join(specrailsDir, 'backlog-config.json'),
|
|
110
|
+
profilesDir: path.join(specrailsDir, 'profiles'),
|
|
111
|
+
pluginsStateDir: path.join(specrailsDir, 'plugins'),
|
|
112
|
+
fileSummariesDir: path.join(specrailsDir, 'file-summaries'),
|
|
113
|
+
providers: [],
|
|
114
|
+
primaryProvider: '',
|
|
115
|
+
source: 'legacy',
|
|
116
|
+
isLegacy: true,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
/** Flatten a stored entry into a Resolution. */
|
|
120
|
+
export function entryToResolution(key, entry) {
|
|
121
|
+
return {
|
|
122
|
+
key,
|
|
123
|
+
repoPath: entry.repoPath,
|
|
124
|
+
slug: entry.slug,
|
|
125
|
+
workspaceDir: entry.workspaceDir,
|
|
126
|
+
artifactRoot: entry.artifactRoot,
|
|
127
|
+
codeRoot: entry.codeRoot,
|
|
128
|
+
stateDir: entry.stateDir,
|
|
129
|
+
ticketsPath: entry.ticketsPath,
|
|
130
|
+
backlogConfigPath: entry.backlogConfigPath,
|
|
131
|
+
profilesDir: entry.profilesDir,
|
|
132
|
+
pluginsStateDir: entry.pluginsStateDir,
|
|
133
|
+
fileSummariesDir: entry.fileSummariesDir,
|
|
134
|
+
providers: entry.providers,
|
|
135
|
+
primaryProvider: entry.primaryProvider,
|
|
136
|
+
source: entry.source,
|
|
137
|
+
isLegacy: false,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Total, fail-open read. A missing file, a parse error, or a `schemaVersion`
|
|
142
|
+
* greater than we understand all yield an empty registry, so a caller treats
|
|
143
|
+
* it as "no entry" and (if allocating) writes a fresh, understood entry rather
|
|
144
|
+
* than crashing. Biases toward availability over strict consistency — correct
|
|
145
|
+
* for a local, inspectable file.
|
|
146
|
+
*/
|
|
147
|
+
export function readRegistryOrEmpty(home) {
|
|
148
|
+
const empty = { schemaVersion: REGISTRY_SCHEMA_VERSION, projects: {} };
|
|
149
|
+
const p = registryPath(home);
|
|
150
|
+
if (!existsSync(p))
|
|
151
|
+
return empty;
|
|
152
|
+
try {
|
|
153
|
+
const parsed = JSON.parse(readFileSync(p, 'utf8'));
|
|
154
|
+
if (!parsed ||
|
|
155
|
+
typeof parsed !== 'object' ||
|
|
156
|
+
typeof parsed.schemaVersion !== 'number' ||
|
|
157
|
+
parsed.schemaVersion > REGISTRY_SCHEMA_VERSION ||
|
|
158
|
+
typeof parsed.projects !== 'object' ||
|
|
159
|
+
parsed.projects === null) {
|
|
160
|
+
return empty;
|
|
161
|
+
}
|
|
162
|
+
return parsed;
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
return empty;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
/** Write `data` to `filePath` atomically: temp file in the same dir, fsync,
|
|
169
|
+
* rename. A reader (even without the lock) only ever sees a complete old or
|
|
170
|
+
* new file — the lock serialises writers, the rename protects readers. */
|
|
171
|
+
export function atomicWrite(filePath, data) {
|
|
172
|
+
const dir = path.dirname(filePath);
|
|
173
|
+
mkdirSync(dir, { recursive: true });
|
|
174
|
+
// Deterministic-but-unique temp name (no Math.random/Date dependency for the
|
|
175
|
+
// name itself); collisions are impossible because the lock serialises writers.
|
|
176
|
+
const tmp = path.join(dir, `.${path.basename(filePath)}.tmp-${process.pid}`);
|
|
177
|
+
const fd = openSync(tmp, 'w');
|
|
178
|
+
try {
|
|
179
|
+
writeFileSync(fd, data);
|
|
180
|
+
fsyncSync(fd);
|
|
181
|
+
}
|
|
182
|
+
finally {
|
|
183
|
+
closeSync(fd);
|
|
184
|
+
}
|
|
185
|
+
renameSync(tmp, filePath);
|
|
186
|
+
}
|
|
187
|
+
/** Synchronous sleep without busy-spinning the CPU. */
|
|
188
|
+
function syncSleep(ms) {
|
|
189
|
+
const shared = new Int32Array(new SharedArrayBuffer(4));
|
|
190
|
+
Atomics.wait(shared, 0, 0, ms);
|
|
191
|
+
}
|
|
192
|
+
const LOCK_STALE_MS = 30_000;
|
|
193
|
+
const LOCK_SPIN_MS = 50;
|
|
194
|
+
const LOCK_MAX_WAIT_MS = 2_000;
|
|
195
|
+
/**
|
|
196
|
+
* Run `fn` while holding an advisory lock over the registry. Mutual exclusion
|
|
197
|
+
* is an exclusive-create lock file (`open(..., 'wx')`) with bounded spin-retry
|
|
198
|
+
* and stale-lock breaking (a lock whose mtime exceeds the TTL is reclaimed —
|
|
199
|
+
* covers a crashed writer). Read-only callers never take the lock.
|
|
200
|
+
*/
|
|
201
|
+
export function withFileLock(home, fn) {
|
|
202
|
+
const lp = lockPath(home);
|
|
203
|
+
mkdirSync(path.dirname(lp), { recursive: true });
|
|
204
|
+
let fd;
|
|
205
|
+
// Unique per-acquisition token written INTO the lock file. On release we only
|
|
206
|
+
// unlink the file when the token still matches OURS — so a writer whose lock
|
|
207
|
+
// was stale-broken (TTL-reclaimed) by another writer never deletes the NEW
|
|
208
|
+
// owner's lock out from under it (which would let a third writer acquire
|
|
209
|
+
// concurrently and race the read-modify-write).
|
|
210
|
+
const ourToken = `${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
211
|
+
const deadline = Date.now() + LOCK_MAX_WAIT_MS;
|
|
212
|
+
for (;;) {
|
|
213
|
+
try {
|
|
214
|
+
fd = openSync(lp, 'wx');
|
|
215
|
+
try {
|
|
216
|
+
writeFileSync(fd, ourToken);
|
|
217
|
+
fsyncSync(fd);
|
|
218
|
+
}
|
|
219
|
+
catch {
|
|
220
|
+
/* best-effort — the lock still serialises via exclusive-create */
|
|
221
|
+
}
|
|
222
|
+
break;
|
|
223
|
+
}
|
|
224
|
+
catch {
|
|
225
|
+
// Lock held — break it if stale, otherwise spin until the deadline.
|
|
226
|
+
try {
|
|
227
|
+
const age = Date.now() - statSync(lp).mtimeMs;
|
|
228
|
+
if (age > LOCK_STALE_MS) {
|
|
229
|
+
unlinkSync(lp);
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
catch {
|
|
234
|
+
// lock vanished between open and stat — retry immediately
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
if (Date.now() >= deadline) {
|
|
238
|
+
// FAIL CLOSED: proceeding without the lock would run `fn()` (a
|
|
239
|
+
// read-modify-write of the registry) with NO exclusivity, so a
|
|
240
|
+
// concurrent core+desktop writer can lost-update the registry. Throw
|
|
241
|
+
// instead of silently dropping mutual exclusivity — callers wrap this as
|
|
242
|
+
// non-fatal (the registry update is retried / skipped).
|
|
243
|
+
throw new Error('registry lock acquisition timed out');
|
|
244
|
+
}
|
|
245
|
+
syncSleep(LOCK_SPIN_MS);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
try {
|
|
249
|
+
return fn();
|
|
250
|
+
}
|
|
251
|
+
finally {
|
|
252
|
+
if (fd !== undefined) {
|
|
253
|
+
try {
|
|
254
|
+
closeSync(fd);
|
|
255
|
+
}
|
|
256
|
+
catch {
|
|
257
|
+
/* already closed */
|
|
258
|
+
}
|
|
259
|
+
// Only unlink the lock when it still carries OUR token. If we were
|
|
260
|
+
// stale-broken and another writer now owns the file, its token differs and
|
|
261
|
+
// we leave it alone (a missing/unreadable file ⇒ already gone ⇒ no-op).
|
|
262
|
+
try {
|
|
263
|
+
const onDisk = readFileSync(lp, 'utf8');
|
|
264
|
+
if (onDisk === ourToken) {
|
|
265
|
+
unlinkSync(lp);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
catch {
|
|
269
|
+
/* already removed / unreadable — nothing to clean up */
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
/** Deterministic slug allocation: basename-derived, `-N` dedup suffix. Both
|
|
275
|
+
* tools implement this identically so they only ever diverge on the suffix,
|
|
276
|
+
* which read-before-allocate resolves. */
|
|
277
|
+
export function allocateSlug(canon, existing) {
|
|
278
|
+
let base = slugify(path.basename(canon));
|
|
279
|
+
if (base === '')
|
|
280
|
+
base = 'project';
|
|
281
|
+
if (!existing.has(base))
|
|
282
|
+
return base;
|
|
283
|
+
let n = 2;
|
|
284
|
+
while (existing.has(`${base}-${n}`))
|
|
285
|
+
n += 1;
|
|
286
|
+
return `${base}-${n}`;
|
|
287
|
+
}
|
|
288
|
+
function buildEntry(home, canon, slug, opts) {
|
|
289
|
+
const providers = opts.providers && opts.providers.length > 0 ? opts.providers : ['claude'];
|
|
290
|
+
const now = opts.now ?? new Date().toISOString();
|
|
291
|
+
const layout = workspaceLayout(home, slug, canon);
|
|
292
|
+
const entry = {
|
|
293
|
+
...layout,
|
|
294
|
+
providers,
|
|
295
|
+
primaryProvider: providers[0],
|
|
296
|
+
coreVersion: opts.coreVersion,
|
|
297
|
+
createdAt: now,
|
|
298
|
+
lastInstallAt: now,
|
|
299
|
+
source: opts.allocator ?? 'core-standalone',
|
|
300
|
+
};
|
|
301
|
+
if (opts.desktopProjectId)
|
|
302
|
+
entry.desktopProjectId = opts.desktopProjectId;
|
|
303
|
+
return entry;
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* The shared resolver both tools run. Given a repo path, returns where that
|
|
307
|
+
* repo's artifacts live. With `allocate:true`, creates + persists an entry when
|
|
308
|
+
* none exists (under the lock, with a re-read double-check); readers pass
|
|
309
|
+
* `allocate:false` and fall back to the in-repo legacy layout when absent.
|
|
310
|
+
*/
|
|
311
|
+
export function resolveArtifacts(repoPathInput, opts = {}) {
|
|
312
|
+
const home = opts.home;
|
|
313
|
+
const canon = canonicalizeRepoPath(repoPathInput);
|
|
314
|
+
const key = normalizeKey(canon);
|
|
315
|
+
// Fast path: lock-free read for a pure lookup.
|
|
316
|
+
const reg = readRegistryOrEmpty(home);
|
|
317
|
+
const existing = reg.projects[key];
|
|
318
|
+
if (existing)
|
|
319
|
+
return entryToResolution(key, existing);
|
|
320
|
+
if (!opts.allocate)
|
|
321
|
+
return legacyResolution(canon);
|
|
322
|
+
// Allocation path: lock, re-read, double-check, atomic write.
|
|
323
|
+
return withFileLock(home, () => {
|
|
324
|
+
const fresh = readRegistryOrEmpty(home);
|
|
325
|
+
const raced = fresh.projects[key];
|
|
326
|
+
if (raced)
|
|
327
|
+
return entryToResolution(key, raced);
|
|
328
|
+
const existingSlugs = new Set(Object.values(fresh.projects).map((e) => e.slug));
|
|
329
|
+
const slug = allocateSlug(canon, existingSlugs);
|
|
330
|
+
const entry = buildEntry(resolveHome(home), canon, slug, opts);
|
|
331
|
+
fresh.projects[key] = entry;
|
|
332
|
+
fresh.schemaVersion = REGISTRY_SCHEMA_VERSION;
|
|
333
|
+
fresh.generator = 'specrails-core';
|
|
334
|
+
fresh.updatedAt = opts.now ?? new Date().toISOString();
|
|
335
|
+
atomicWrite(registryPath(home), JSON.stringify(fresh, null, 2) + '\n');
|
|
336
|
+
return entryToResolution(key, entry);
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
//# sourceMappingURL=registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../../src/installer/util/registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EACL,SAAS,EACT,UAAU,EACV,SAAS,EACT,SAAS,EACT,QAAQ,EACR,YAAY,EACZ,YAAY,EACZ,UAAU,EACV,QAAQ,EACR,UAAU,EACV,aAAa,GACd,MAAM,SAAS,CAAA;AAChB,OAAO,EAAE,MAAM,SAAS,CAAA;AACxB,OAAO,IAAI,MAAM,WAAW,CAAA;AAE5B;4DAC4D;AAC5D,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC,CAAA;AA0FxC;;;GAGG;AACH,MAAM,UAAU,OAAO,CAAC,IAAY;IAClC,OAAO,IAAI,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,aAAa,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAA;AAC7E,CAAC;AAED,uDAAuD;AACvD,MAAM,UAAU,WAAW,CAAC,IAAa;IACvC,OAAO,IAAI,IAAI,OAAO,CAAC,GAAG,CAAC,uBAAuB,IAAI,EAAE,CAAC,OAAO,EAAE,CAAA;AACpE,CAAC;AAED,wCAAwC;AACxC,MAAM,UAAU,YAAY,CAAC,IAAa;IACxC,OAAO,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,EAAE,YAAY,EAAE,eAAe,CAAC,CAAA;AACpE,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,aAAa,CAAC,IAAa;IACzC,OAAO,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,EAAE,YAAY,EAAE,WAAW,CAAC,CAAA;AAChE,CAAC;AAED,+CAA+C;AAC/C,MAAM,UAAU,QAAQ,CAAC,IAAa;IACpC,OAAO,YAAY,CAAC,IAAI,CAAC,GAAG,OAAO,CAAA;AACrC,CAAC;AAED;6EAC6E;AAC7E,MAAM,UAAU,YAAY,CAAC,GAAW;IACtC,IAAI,CAAC;QACH,OAAO,YAAY,CAAC,GAAG,CAAC,CAAA;IAC1B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,GAAG,CAAA;IACZ,CAAC;AACH,CAAC;AAED;;wDAEwD;AACxD,MAAM,UAAU,YAAY,CAAC,KAAa;IACxC,IAAI,OAAO,CAAC,QAAQ,KAAK,QAAQ,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;QAClE,OAAO,KAAK,CAAC,WAAW,EAAE,CAAA;IAC5B,CAAC;IACD,OAAO,KAAK,CAAA;AACd,CAAC;AAED,oFAAoF;AACpF,MAAM,UAAU,oBAAoB,CAAC,aAAqB;IACxD,OAAO,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC,CAAA;AAClD,CAAC;AAED;+DAC+D;AAC/D,MAAM,UAAU,eAAe,CAAC,IAAY,EAAE,IAAY,EAAE,KAAa;IAIvE,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,EAAE,YAAY,EAAE,UAAU,EAAE,IAAI,EAAE,WAAW,CAAC,CAAA;IAC9F,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,YAAY,CAAC,CAAA;IAC1D,OAAO;QACL,QAAQ,EAAE,KAAK;QACf,IAAI;QACJ,YAAY;QACZ,YAAY,EAAE,YAAY;QAC1B,QAAQ,EAAE,KAAK;QACf,QAAQ,EAAE,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,SAAS,CAAC;QAC5C,WAAW,EAAE,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,oBAAoB,CAAC;QAC1D,iBAAiB,EAAE,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,qBAAqB,CAAC;QACjE,WAAW,EAAE,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,UAAU,CAAC;QAChD,eAAe,EAAE,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,SAAS,CAAC;QACnD,gBAAgB,EAAE,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,gBAAgB,CAAC;KAC5D,CAAA;AACH,CAAC;AAED,kFAAkF;AAClF,MAAM,UAAU,gBAAgB,CAAC,KAAa;IAC5C,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,YAAY,CAAC,CAAA;IACnD,OAAO;QACL,GAAG,EAAE,YAAY,CAAC,KAAK,CAAC;QACxB,QAAQ,EAAE,KAAK;QACf,IAAI,EAAE,EAAE;QACR,YAAY,EAAE,KAAK;QACnB,YAAY,EAAE,KAAK;QACnB,QAAQ,EAAE,KAAK;QACf,QAAQ,EAAE,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,SAAS,CAAC;QACrC,WAAW,EAAE,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,oBAAoB,CAAC;QAC1D,iBAAiB,EAAE,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,qBAAqB,CAAC;QACjE,WAAW,EAAE,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,UAAU,CAAC;QAChD,eAAe,EAAE,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,SAAS,CAAC;QACnD,gBAAgB,EAAE,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,gBAAgB,CAAC;QAC3D,SAAS,EAAE,EAAE;QACb,eAAe,EAAE,EAAE;QACnB,MAAM,EAAE,QAAQ;QAChB,QAAQ,EAAE,IAAI;KACf,CAAA;AACH,CAAC;AAED,gDAAgD;AAChD,MAAM,UAAU,iBAAiB,CAAC,GAAW,EAAE,KAAmB;IAChE,OAAO;QACL,GAAG;QACH,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,YAAY,EAAE,KAAK,CAAC,YAAY;QAChC,YAAY,EAAE,KAAK,CAAC,YAAY;QAChC,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,WAAW,EAAE,KAAK,CAAC,WAAW;QAC9B,iBAAiB,EAAE,KAAK,CAAC,iBAAiB;QAC1C,WAAW,EAAE,KAAK,CAAC,WAAW;QAC9B,eAAe,EAAE,KAAK,CAAC,eAAe;QACtC,gBAAgB,EAAE,KAAK,CAAC,gBAAgB;QACxC,SAAS,EAAE,KAAK,CAAC,SAAS;QAC1B,eAAe,EAAE,KAAK,CAAC,eAAe;QACtC,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,QAAQ,EAAE,KAAK;KAChB,CAAA;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAa;IAC/C,MAAM,KAAK,GAAiB,EAAE,aAAa,EAAE,uBAAuB,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAA;IACpF,MAAM,CAAC,GAAG,YAAY,CAAC,IAAI,CAAC,CAAA;IAC5B,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;QAAE,OAAO,KAAK,CAAA;IAChC,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,EAAE,MAAM,CAAC,CAAiB,CAAA;QAClE,IACE,CAAC,MAAM;YACP,OAAO,MAAM,KAAK,QAAQ;YAC1B,OAAO,MAAM,CAAC,aAAa,KAAK,QAAQ;YACxC,MAAM,CAAC,aAAa,GAAG,uBAAuB;YAC9C,OAAO,MAAM,CAAC,QAAQ,KAAK,QAAQ;YACnC,MAAM,CAAC,QAAQ,KAAK,IAAI,EACxB,CAAC;YACD,OAAO,KAAK,CAAA;QACd,CAAC;QACD,OAAO,MAAM,CAAA;IACf,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAA;IACd,CAAC;AACH,CAAC;AAED;;2EAE2E;AAC3E,MAAM,UAAU,WAAW,CAAC,QAAgB,EAAE,IAAY;IACxD,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAA;IAClC,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;IACnC,6EAA6E;IAC7E,+EAA+E;IAC/E,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,QAAQ,OAAO,CAAC,GAAG,EAAE,CAAC,CAAA;IAC5E,MAAM,EAAE,GAAG,QAAQ,CAAC,GAAG,EAAE,GAAG,CAAC,CAAA;IAC7B,IAAI,CAAC;QACH,aAAa,CAAC,EAAE,EAAE,IAAI,CAAC,CAAA;QACvB,SAAS,CAAC,EAAE,CAAC,CAAA;IACf,CAAC;YAAS,CAAC;QACT,SAAS,CAAC,EAAE,CAAC,CAAA;IACf,CAAC;IACD,UAAU,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAA;AAC3B,CAAC;AAED,uDAAuD;AACvD,SAAS,SAAS,CAAC,EAAU;IAC3B,MAAM,MAAM,GAAG,IAAI,UAAU,CAAC,IAAI,iBAAiB,CAAC,CAAC,CAAC,CAAC,CAAA;IACvD,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAA;AAChC,CAAC;AAED,MAAM,aAAa,GAAG,MAAM,CAAA;AAC5B,MAAM,YAAY,GAAG,EAAE,CAAA;AACvB,MAAM,gBAAgB,GAAG,KAAK,CAAA;AAE9B;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAAI,IAAwB,EAAE,EAAW;IACnE,MAAM,EAAE,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAA;IACzB,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;IAChD,IAAI,EAAsB,CAAA;IAC1B,8EAA8E;IAC9E,6EAA6E;IAC7E,2EAA2E;IAC3E,yEAAyE;IACzE,gDAAgD;IAChD,MAAM,QAAQ,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,EAAE,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAA;IACtF,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,gBAAgB,CAAA;IAC9C,SAAS,CAAC;QACR,IAAI,CAAC;YACH,EAAE,GAAG,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC,CAAA;YACvB,IAAI,CAAC;gBACH,aAAa,CAAC,EAAE,EAAE,QAAQ,CAAC,CAAA;gBAC3B,SAAS,CAAC,EAAE,CAAC,CAAA;YACf,CAAC;YAAC,MAAM,CAAC;gBACP,kEAAkE;YACpE,CAAC;YACD,MAAK;QACP,CAAC;QAAC,MAAM,CAAC;YACP,oEAAoE;YACpE,IAAI,CAAC;gBACH,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,CAAC,EAAE,CAAC,CAAC,OAAO,CAAA;gBAC7C,IAAI,GAAG,GAAG,aAAa,EAAE,CAAC;oBACxB,UAAU,CAAC,EAAE,CAAC,CAAA;oBACd,SAAQ;gBACV,CAAC;YACH,CAAC;YAAC,MAAM,CAAC;gBACP,0DAA0D;gBAC1D,SAAQ;YACV,CAAC;YACD,IAAI,IAAI,CAAC,GAAG,EAAE,IAAI,QAAQ,EAAE,CAAC;gBAC3B,+DAA+D;gBAC/D,+DAA+D;gBAC/D,qEAAqE;gBACrE,yEAAyE;gBACzE,wDAAwD;gBACxD,MAAM,IAAI,KAAK,CAAC,qCAAqC,CAAC,CAAA;YACxD,CAAC;YACD,SAAS,CAAC,YAAY,CAAC,CAAA;QACzB,CAAC;IACH,CAAC;IACD,IAAI,CAAC;QACH,OAAO,EAAE,EAAE,CAAA;IACb,CAAC;YAAS,CAAC;QACT,IAAI,EAAE,KAAK,SAAS,EAAE,CAAC;YACrB,IAAI,CAAC;gBACH,SAAS,CAAC,EAAE,CAAC,CAAA;YACf,CAAC;YAAC,MAAM,CAAC;gBACP,oBAAoB;YACtB,CAAC;YACD,mEAAmE;YACnE,2EAA2E;YAC3E,wEAAwE;YACxE,IAAI,CAAC;gBACH,MAAM,MAAM,GAAG,YAAY,CAAC,EAAE,EAAE,MAAM,CAAC,CAAA;gBACvC,IAAI,MAAM,KAAK,QAAQ,EAAE,CAAC;oBACxB,UAAU,CAAC,EAAE,CAAC,CAAA;gBAChB,CAAC;YACH,CAAC;YAAC,MAAM,CAAC;gBACP,wDAAwD;YAC1D,CAAC;QACH,CAAC;IACH,CAAC;AACH,CAAC;AAED;;2CAE2C;AAC3C,MAAM,UAAU,YAAY,CAAC,KAAa,EAAE,QAA6B;IACvE,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAA;IACxC,IAAI,IAAI,KAAK,EAAE;QAAE,IAAI,GAAG,SAAS,CAAA;IACjC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAA;IACpC,IAAI,CAAC,GAAG,CAAC,CAAA;IACT,OAAO,QAAQ,CAAC,GAAG,CAAC,GAAG,IAAI,IAAI,CAAC,EAAE,CAAC;QAAE,CAAC,IAAI,CAAC,CAAA;IAC3C,OAAO,GAAG,IAAI,IAAI,CAAC,EAAE,CAAA;AACvB,CAAC;AAED,SAAS,UAAU,CACjB,IAAY,EACZ,KAAa,EACb,IAAY,EACZ,IAAoB;IAEpB,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,IAAI,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAA;IAC3F,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAA;IAChD,MAAM,MAAM,GAAG,eAAe,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,CAAC,CAAA;IACjD,MAAM,KAAK,GAAiB;QAC1B,GAAG,MAAM;QACT,SAAS;QACT,eAAe,EAAE,SAAS,CAAC,CAAC,CAAE;QAC9B,WAAW,EAAE,IAAI,CAAC,WAAW;QAC7B,SAAS,EAAE,GAAG;QACd,aAAa,EAAE,GAAG;QAClB,MAAM,EAAE,IAAI,CAAC,SAAS,IAAI,iBAAiB;KAC5C,CAAA;IACD,IAAI,IAAI,CAAC,gBAAgB;QAAE,KAAK,CAAC,gBAAgB,GAAG,IAAI,CAAC,gBAAgB,CAAA;IACzE,OAAO,KAAK,CAAA;AACd,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,aAAqB,EAAE,OAAuB,EAAE;IAC/E,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAA;IACtB,MAAM,KAAK,GAAG,oBAAoB,CAAC,aAAa,CAAC,CAAA;IACjD,MAAM,GAAG,GAAG,YAAY,CAAC,KAAK,CAAC,CAAA;IAE/B,+CAA+C;IAC/C,MAAM,GAAG,GAAG,mBAAmB,CAAC,IAAI,CAAC,CAAA;IACrC,MAAM,QAAQ,GAAG,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAA;IAClC,IAAI,QAAQ;QAAE,OAAO,iBAAiB,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAA;IAErD,IAAI,CAAC,IAAI,CAAC,QAAQ;QAAE,OAAO,gBAAgB,CAAC,KAAK,CAAC,CAAA;IAElD,8DAA8D;IAC9D,OAAO,YAAY,CAAC,IAAI,EAAE,GAAG,EAAE;QAC7B,MAAM,KAAK,GAAG,mBAAmB,CAAC,IAAI,CAAC,CAAA;QACvC,MAAM,KAAK,GAAG,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAA;QACjC,IAAI,KAAK;YAAE,OAAO,iBAAiB,CAAC,GAAG,EAAE,KAAK,CAAC,CAAA;QAE/C,MAAM,aAAa,GAAG,IAAI,GAAG,CAAS,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAA;QACvF,MAAM,IAAI,GAAG,YAAY,CAAC,KAAK,EAAE,aAAa,CAAC,CAAA;QAC/C,MAAM,KAAK,GAAG,UAAU,CAAC,WAAW,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,CAAC,CAAA;QAC9D,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,KAAK,CAAA;QAC3B,KAAK,CAAC,aAAa,GAAG,uBAAuB,CAAA;QAC7C,KAAK,CAAC,SAAS,GAAG,gBAAgB,CAAA;QAClC,KAAK,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,IAAI,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAA;QACtD,WAAW,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,CAAA;QACtE,OAAO,iBAAiB,CAAC,GAAG,EAAE,KAAK,CAAC,CAAA;IACtC,CAAC,CAAC,CAAA;AACJ,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "specrails-core",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.9.1",
|
|
4
4
|
"description": "AI agent workflow system for Claude Code — installs 12 specialized agents, orchestration commands, and persona-driven product discovery into any repository",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -56,6 +56,7 @@
|
|
|
56
56
|
"test": "npm run typecheck && vitest run",
|
|
57
57
|
"test:watch": "vitest",
|
|
58
58
|
"test:coverage": "vitest run --coverage",
|
|
59
|
+
"dogfood": "npm run build && node bin/specrails-core.mjs init --yes",
|
|
59
60
|
"prepack": "npm run build"
|
|
60
61
|
},
|
|
61
62
|
"dependencies": {
|
package/pinned-versions.json
CHANGED
|
@@ -47,6 +47,10 @@ You are the kind of architect who can sit in a room with a product owner, fully
|
|
|
47
47
|
|
|
48
48
|
Do not proceed with any design work, file reading, or artifact creation until specName is confirmed.
|
|
49
49
|
|
|
50
|
+
## Repository location (read first)
|
|
51
|
+
|
|
52
|
+
Your working directory may NOT be the user's source repository. The user's source code, `openspec/**`, `.claude/rules/`, and `.git` all live under **`${SPECRAILS_REPO_DIR:-.}`** (the spawner sets the env var to the repo path; unset defaults to `.`, i.e. byte-identical to a classic in-repo run). Read specs from `${SPECRAILS_REPO_DIR:-.}/openspec/...`, scan conventions under `${SPECRAILS_REPO_DIR:-.}/.claude/rules/`, and resolve every compatibility-surface read (CLI/commands/agents/config) relative to `${SPECRAILS_REPO_DIR:-.}`.
|
|
53
|
+
|
|
50
54
|
## Core Responsibilities
|
|
51
55
|
|
|
52
56
|
When invoked by the orchestrator with a specName argument, you must execute the following steps in order:
|
|
@@ -71,7 +75,7 @@ This must be a real Skill tool invocation that appears in your transcript — no
|
|
|
71
75
|
- "Change already exists" → REUSE it and continue (the step is idempotent). Do NOT call `opsx:new` first.
|
|
72
76
|
|
|
73
77
|
**3 — PROOF-OF-EXECUTION gate.** After `opsx:ff` returns, verify it really ran by reading the authoritative status — not a hardcoded file list:
|
|
74
|
-
- Run `openspec status --change "<specName>" --json
|
|
78
|
+
- Run `(cd "${SPECRAILS_REPO_DIR:-.}" && openspec status --change "<specName>" --json)` (the OpenSpec project root is `${SPECRAILS_REPO_DIR:-.}`, not the workspace cwd). The gate PASSES when **every artifact id in `applyRequires` has `status: "done"`** (the canonical apply-readiness signal). `tasks.md` is always among them — confirm `${SPECRAILS_REPO_DIR:-.}/openspec/changes/<specName>/tasks.md` exists and is non-empty. The other artifacts the schema generates (typically `proposal.md`, `design.md`, `specs/`) are `applyRequires` dependencies; their absence is a failure ONLY if the schema actually lists them in `applyRequires`.
|
|
75
79
|
|
|
76
80
|
If the gate does NOT pass, the scaffold was simulated or incomplete — **do NOT hand-author the artifacts to cover it.** Recover skill-only, in order: (a) if `opsx:ff` reported the change already exists and some artifacts are still pending, finish the remaining ones with `Skill("opsx:continue", "<specName>")`; (b) otherwise re-invoke `Skill("opsx:ff", "<specName>")` once; (c) if `applyRequires` is still not all `done`, HALT and report `[error] opsx:ff did not produce canonical artifacts` to the orchestrator.
|
|
77
81
|
|
|
@@ -82,8 +86,8 @@ If the gate does NOT pass, the scaffold was simulated or incomplete — **do NOT
|
|
|
82
86
|
Only after Step 0's proof gate passes do you proceed to Steps 1–6.
|
|
83
87
|
|
|
84
88
|
### 1. Analyze Spec Changes
|
|
85
|
-
- Read all relevant specs from
|
|
86
|
-
- Read pending changes from
|
|
89
|
+
- Read all relevant specs from `${SPECRAILS_REPO_DIR:-.}/openspec/specs/` — this is the **source of truth**
|
|
90
|
+
- Read pending changes from `${SPECRAILS_REPO_DIR:-.}/openspec/changes/<name>/`
|
|
87
91
|
- Understand the full context: what changed, why it changed, and what it impacts
|
|
88
92
|
- Cross-reference with existing specs
|
|
89
93
|
|
|
@@ -114,7 +118,7 @@ This project follows this architecture:
|
|
|
114
118
|
{{LAYER_CONVENTIONS}}
|
|
115
119
|
|
|
116
120
|
- Always check scoped context: {{LAYER_CLAUDE_MD_PATHS}}
|
|
117
|
-
- Always check
|
|
121
|
+
- Always check `${SPECRAILS_REPO_DIR:-.}/.claude/rules/` for conditional conventions per layer
|
|
118
122
|
|
|
119
123
|
### 5. Key Warnings to Always Consider
|
|
120
124
|
{{WARNINGS}}
|
|
@@ -125,12 +129,12 @@ After producing the task breakdown and before finalizing output:
|
|
|
125
129
|
|
|
126
130
|
1. **Extract the proposed surface changes** from your implementation design: which commands, agents, placeholders, flags, or config keys are being added, removed, renamed, or modified?
|
|
127
131
|
|
|
128
|
-
2. **Compare against the current surface** by reading:
|
|
129
|
-
-
|
|
130
|
-
-
|
|
131
|
-
-
|
|
132
|
-
-
|
|
133
|
-
-
|
|
132
|
+
2. **Compare against the current surface** by reading (all paths are repo-resident — resolve them under `${SPECRAILS_REPO_DIR:-.}`):
|
|
133
|
+
- `${SPECRAILS_REPO_DIR:-.}/bin/specrails-core.mjs` for CLI flags
|
|
134
|
+
- `${SPECRAILS_REPO_DIR:-.}/templates/commands/*.md` for command names and argument flags
|
|
135
|
+
- `${SPECRAILS_REPO_DIR:-.}/templates/agents/*.md` for agent names
|
|
136
|
+
- `${SPECRAILS_REPO_DIR:-.}/templates/**/*.md` for `{{PLACEHOLDER}}` keys
|
|
137
|
+
- `${SPECRAILS_REPO_DIR:-.}/openspec/config.yaml` for config keys
|
|
134
138
|
|
|
135
139
|
3. **Classify each change** using the four categories:
|
|
136
140
|
- Category 1: Removal (BREAKING — the element no longer exists; example: a CLI flag is deleted)
|
|
@@ -8,6 +8,8 @@ memory: project
|
|
|
8
8
|
|
|
9
9
|
You are a backend specialist — expert in {{BACKEND_TECH_LIST}}. You implement backend and core logic tasks with surgical precision.
|
|
10
10
|
|
|
11
|
+
**Repository location.** Your working directory may NOT be the source repo. `openspec/**` and the source files named in `tasks.md` (repo-relative paths) live under `${SPECRAILS_REPO_DIR:-.}` (unset ⇒ `.` ⇒ classic in-repo run). Read openspec from `${SPECRAILS_REPO_DIR:-.}/openspec/...` and edit every source file as `${SPECRAILS_REPO_DIR:-.}/<path>`; run CI/build/test from `cd "${SPECRAILS_REPO_DIR:-.}"`.
|
|
12
|
+
|
|
11
13
|
## Your Expertise
|
|
12
14
|
|
|
13
15
|
{{BACKEND_EXPERTISE}}
|
|
@@ -38,7 +40,7 @@ A real Skill invocation in your transcript, not a description. `opsx:apply` walk
|
|
|
38
40
|
|
|
39
41
|
**2 — UNATTENDED pre-authorization.** Never emit `AskUserQuestion`; never wait for input. Change selection → `<specName>`. Ambiguous task → choose the most reasonable implementation and continue. Design issue surfaced → note it, resolve reasonably, continue. Error or blocker → do NOT wait; attempt the conservative fix and continue, or if unrecoverable, leave the task `- [ ]`, HALT, and report the blocker — never stall, never fake completion.
|
|
40
42
|
|
|
41
|
-
**3 — PROOF-OF-EXECUTION gate.** Before you finish, every task you own in
|
|
43
|
+
**3 — PROOF-OF-EXECUTION gate.** Before you finish, every task you own in `${SPECRAILS_REPO_DIR:-.}/openspec/changes/<specName>/tasks.md` must be `- [x]` AND backed by real changes. If `- [ ]` items remain, re-enter the apply loop — do NOT hand-flip checkboxes.
|
|
42
44
|
|
|
43
45
|
**4 — Execution receipt.** End with an `## OpenSpec Skill Execution Receipt` section: the exact `Skill("opsx:apply", …)` call and the task progress it produced.
|
|
44
46
|
|
|
@@ -50,7 +52,7 @@ A real Skill invocation in your transcript, not a description. `opsx:apply` walk
|
|
|
50
52
|
```bash
|
|
51
53
|
{{CI_COMMANDS_BACKEND}}
|
|
52
54
|
```
|
|
53
|
-
4. **Commit
|
|
55
|
+
4. **Commit** (against the repo): `git -C "${SPECRAILS_REPO_DIR:-.}" add -A && git -C "${SPECRAILS_REPO_DIR:-.}" commit -m "feat: <change-name>"`
|
|
54
56
|
|
|
55
57
|
## Critical Rules
|
|
56
58
|
|
|
@@ -50,18 +50,30 @@ You are a polyglot engineer with extraordinary depth in:
|
|
|
50
50
|
|
|
51
51
|
You don't just write code that works — you write code that is elegant, maintainable, testable, and performant.
|
|
52
52
|
|
|
53
|
+
## Repository location (read first)
|
|
54
|
+
|
|
55
|
+
Your working directory may NOT be the user's source repository. The user's source code, `openspec/**`, the project `CLAUDE.md`, `.claude/rules/`, and `.git` all live under **`${SPECRAILS_REPO_DIR:-.}`** (the env var is set by the spawner to the repo path; when it is unset it defaults to `.`, i.e. the current directory — byte-identical to a classic in-repo run).
|
|
56
|
+
|
|
57
|
+
Concretely:
|
|
58
|
+
- **Every openspec read/write** targets `${SPECRAILS_REPO_DIR:-.}/openspec/...`.
|
|
59
|
+
- **Every source-file edit** named in `tasks.md` uses repo-relative paths (e.g. `src/foo.ts`); resolve and edit them as `${SPECRAILS_REPO_DIR:-.}/<path>` so the change lands in the real repo, not the working directory.
|
|
60
|
+
- **CI / build / test commands** run from inside the repo — `cd "${SPECRAILS_REPO_DIR:-.}"` (or run them with that as the working directory) before invoking them.
|
|
61
|
+
- **Convention scans** of the project `CLAUDE.md` and `.claude/rules/` read from `${SPECRAILS_REPO_DIR:-.}`.
|
|
62
|
+
|
|
63
|
+
Run-state directories you write to during a run — `.claude/agent-memory/`, `.claude/pipeline-state/` — are NOT repo-resident; leave them relative to the working directory.
|
|
64
|
+
|
|
53
65
|
## Your Mission
|
|
54
66
|
|
|
55
67
|
When an OpenSpec change is being applied, you:
|
|
56
|
-
1. **Read and deeply understand the change specification** in
|
|
57
|
-
2. **Read the relevant base specs** in
|
|
58
|
-
3. **Consult existing codebase conventions** from CLAUDE.md files,
|
|
68
|
+
1. **Read and deeply understand the change specification** in `${SPECRAILS_REPO_DIR:-.}/openspec/changes/<name>/`
|
|
69
|
+
2. **Read the relevant base specs** in `${SPECRAILS_REPO_DIR:-.}/openspec/specs/` to understand the full context
|
|
70
|
+
3. **Consult existing codebase conventions** from `${SPECRAILS_REPO_DIR:-.}/CLAUDE.md` files, `${SPECRAILS_REPO_DIR:-.}/.claude/rules/`, and existing code patterns
|
|
59
71
|
4. **Implement the changes** with surgical precision across all affected layers
|
|
60
72
|
5. **Ensure consistency** with the existing codebase style, patterns, and architecture
|
|
61
73
|
|
|
62
74
|
## Tool Selection — MCP-First for Codebase Tasks
|
|
63
75
|
|
|
64
|
-
**Mandatory step BEFORE any code-navigation tool call**: scan the project's
|
|
76
|
+
**Mandatory step BEFORE any code-navigation tool call**: scan the project's `${SPECRAILS_REPO_DIR:-.}/CLAUDE.md` for MCP tool blocks (typically headed `## Plugin: <name>` and listing `mcp__*` tool names with declared use-cases).
|
|
65
77
|
|
|
66
78
|
If a project-documented MCP tool's "When to use" matches your current need, you **MUST** call it instead of the built-in equivalent (`Read`, `Grep`, `WebFetch`, etc.). Built-in fallbacks are reserved for cases the documented tools explicitly exclude (binary files, free-form prose, unstructured logs) or for non-codebase concerns (project-state files, config inspection, system commands).
|
|
67
79
|
|
|
@@ -77,7 +89,7 @@ This is non-negotiable for code-navigation work: plugin authors choose tools bec
|
|
|
77
89
|
You MUST follow Test-Driven Development. This is non-negotiable. The cycle is: **Red → Green → Refactor**. Never write production code without a failing test first.
|
|
78
90
|
|
|
79
91
|
### Phase 1: Understand
|
|
80
|
-
- **First, scan the project's
|
|
92
|
+
- **First, scan the project's `${SPECRAILS_REPO_DIR:-.}/CLAUDE.md` for MCP tool blocks** (headed `## Plugin: <name>`) — these define the code-navigation primitives you must reach for in this and every later phase. See "Tool Selection — MCP-First" above. Internalise the available tools BEFORE you start reading files.
|
|
81
93
|
- Read the OpenSpec change spec thoroughly
|
|
82
94
|
- Read referenced base specs
|
|
83
95
|
- Read layer-specific CLAUDE.md files ({{LAYER_CLAUDE_MD_PATHS}})
|
|
@@ -113,13 +125,13 @@ This must be a real Skill tool invocation in your transcript. `opsx:apply` reads
|
|
|
113
125
|
- "Implementation reveals a design issue?" → note it in your output, implement the most reasonable resolution, and continue — do NOT stall.
|
|
114
126
|
- "Error or blocker encountered — pause and wait for guidance?" → do NOT wait. Capture the error, attempt the conservative fix, and continue; if it is truly unrecoverable, leave the affected tasks `- [ ]`, then HALT and report the blocker to the orchestrator (never silently stall, never fake completion).
|
|
115
127
|
|
|
116
|
-
**3 — PROOF-OF-EXECUTION gate.** Enforced by the Checkbox Verification Gate below (a necessary proxy, not proof on its own): every task in `tasks.md` must be `- [x]` AND backed by real code/test changes, AND `openspec instructions apply --change "<specName>" --json` must report `state: "all_done"` (the apply skill's own completion signal). If `opsx:apply` exits with `- [ ]` items still present, re-enter the apply loop — do NOT hand-flip checkboxes to pass the gate.
|
|
128
|
+
**3 — PROOF-OF-EXECUTION gate.** Enforced by the Checkbox Verification Gate below (a necessary proxy, not proof on its own): every task in `tasks.md` must be `- [x]` AND backed by real code/test changes, AND `(cd "${SPECRAILS_REPO_DIR:-.}" && openspec instructions apply --change "<specName>" --json)` must report `state: "all_done"` (the apply skill's own completion signal; the OpenSpec project root is `${SPECRAILS_REPO_DIR:-.}`, not the workspace cwd). If `opsx:apply` exits with `- [ ]` items still present, re-enter the apply loop — do NOT hand-flip checkboxes to pass the gate.
|
|
117
129
|
|
|
118
130
|
**4 — Execution receipt.** Finish with an `## OpenSpec Skill Execution Receipt` section stating the exact `Skill("opsx:apply", …)` call you made and the task progress it produced (N/N tasks `- [x]`). No receipt with a real Skill call = contract failed.
|
|
119
131
|
|
|
120
132
|
**After `opsx:apply` exits — Checkbox Verification Gate:**
|
|
121
133
|
|
|
122
|
-
1. Read
|
|
134
|
+
1. Read `${SPECRAILS_REPO_DIR:-.}/openspec/changes/<specName>/tasks.md`
|
|
123
135
|
2. Search for any lines matching the pattern `- [ ]` (hyphen, space, open-bracket, space, close-bracket)
|
|
124
136
|
3. **If any `- [ ]` lines are found**: HALT. List every incomplete task title. Do NOT proceed to Phase 4. Report the incomplete tasks to the orchestrator.
|
|
125
137
|
4. **If no `- [ ]` lines remain** (all tasks are `- [x]`): the gate passes — proceed to Phase 4.
|
|
@@ -156,7 +168,7 @@ Follow the project architecture strictly:
|
|
|
156
168
|
|
|
157
169
|
### Phase 4: Verify
|
|
158
170
|
|
|
159
|
-
**Prerequisite: Phase 4 is only reachable if the Phase 3 checkbox verification gate passed** — meaning every task in
|
|
171
|
+
**Prerequisite: Phase 4 is only reachable if the Phase 3 checkbox verification gate passed** — meaning every task in `${SPECRAILS_REPO_DIR:-.}/openspec/changes/<specName>/tasks.md` is marked `- [x]`. If any `- [ ]` items remain, return to Phase 3.
|
|
160
172
|
|
|
161
173
|
**All tests MUST pass before you hand off to the reviewer. This is a hard gate — do not proceed if any test fails.**
|
|
162
174
|
|
|
@@ -8,6 +8,8 @@ memory: project
|
|
|
8
8
|
|
|
9
9
|
You are a frontend specialist — expert in {{FRONTEND_TECH_LIST}}. You implement frontend tasks with pixel-perfect precision.
|
|
10
10
|
|
|
11
|
+
**Repository location.** Your working directory may NOT be the source repo. `openspec/**` and the source files named in `tasks.md` (repo-relative paths) live under `${SPECRAILS_REPO_DIR:-.}` (unset ⇒ `.` ⇒ classic in-repo run). Read openspec from `${SPECRAILS_REPO_DIR:-.}/openspec/...` and edit every source file as `${SPECRAILS_REPO_DIR:-.}/<path>`; run CI/build/test from `cd "${SPECRAILS_REPO_DIR:-.}"`.
|
|
12
|
+
|
|
11
13
|
## Your Expertise
|
|
12
14
|
|
|
13
15
|
{{FRONTEND_EXPERTISE}}
|
|
@@ -38,7 +40,7 @@ A real Skill invocation in your transcript, not a description. `opsx:apply` walk
|
|
|
38
40
|
|
|
39
41
|
**2 — UNATTENDED pre-authorization.** Never emit `AskUserQuestion`; never wait for input. Change selection → `<specName>`. Ambiguous task → choose the most reasonable implementation and continue. Design issue surfaced → note it, resolve reasonably, continue. Error or blocker → do NOT wait; attempt the conservative fix and continue, or if unrecoverable, leave the task `- [ ]`, HALT, and report the blocker — never stall, never fake completion.
|
|
40
42
|
|
|
41
|
-
**3 — PROOF-OF-EXECUTION gate.** Before you finish, every task you own in
|
|
43
|
+
**3 — PROOF-OF-EXECUTION gate.** Before you finish, every task you own in `${SPECRAILS_REPO_DIR:-.}/openspec/changes/<specName>/tasks.md` must be `- [x]` AND backed by real changes. If `- [ ]` items remain, re-enter the apply loop — do NOT hand-flip checkboxes.
|
|
42
44
|
|
|
43
45
|
**4 — Execution receipt.** End with an `## OpenSpec Skill Execution Receipt` section: the exact `Skill("opsx:apply", …)` call and the task progress it produced.
|
|
44
46
|
|
|
@@ -50,7 +52,7 @@ A real Skill invocation in your transcript, not a description. `opsx:apply` walk
|
|
|
50
52
|
```bash
|
|
51
53
|
{{CI_COMMANDS_FRONTEND}}
|
|
52
54
|
```
|
|
53
|
-
4. **Commit
|
|
55
|
+
4. **Commit** (against the repo): `git -C "${SPECRAILS_REPO_DIR:-.}" add -A && git -C "${SPECRAILS_REPO_DIR:-.}" commit -m "feat: <change-name>"`
|
|
54
56
|
|
|
55
57
|
## Critical Rules
|
|
56
58
|
|
|
@@ -43,6 +43,10 @@ Leave empty to review all areas with equal weight.
|
|
|
43
43
|
|
|
44
44
|
Do not proceed with any review work until specName is confirmed.
|
|
45
45
|
|
|
46
|
+
## Repository location (read first)
|
|
47
|
+
|
|
48
|
+
Your working directory may NOT be the user's source repository. The user's source code, `openspec/**`, and `.git` all live under **`${SPECRAILS_REPO_DIR:-.}`** (the spawner sets the env var to the repo path; unset defaults to `.`, i.e. byte-identical to a classic in-repo run). Read the change spec from `${SPECRAILS_REPO_DIR:-.}/openspec/...`, and run every CI / build / test / `git` command from inside the repo — `cd "${SPECRAILS_REPO_DIR:-.}"` (or use it as the working directory) before invoking them. (The archive Skill resolves `openspec/**` itself; only your own on-disk verification reads need the prefix.)
|
|
49
|
+
|
|
46
50
|
## Your Mission
|
|
47
51
|
|
|
48
52
|
You are the last line of defense between developer output and a PR. You:
|
|
@@ -94,7 +98,7 @@ After running CI checks, also review for:
|
|
|
94
98
|
- Check test quality: tests should assert on behavior, not implementation details
|
|
95
99
|
|
|
96
100
|
### Spec Completeness (mandatory)
|
|
97
|
-
- Read the OpenSpec change spec in
|
|
101
|
+
- Read the OpenSpec change spec in `${SPECRAILS_REPO_DIR:-.}/openspec/changes/<name>/`
|
|
98
102
|
- **Every requirement listed in the spec must have a corresponding implementation** — cross-reference each spec item against the code changes
|
|
99
103
|
- If any spec requirement is missing or only partially implemented, **this is a blocking issue** — flag exactly which requirements are not fulfilled
|
|
100
104
|
- If the developer made assumptions about ambiguous spec items, verify they are reasonable
|
|
@@ -117,7 +121,7 @@ After running CI checks, also review for:
|
|
|
117
121
|
3. **Repeat** up to 3 fix-and-verify cycles
|
|
118
122
|
4. **Report** a summary of what passed, what failed, and what you fixed
|
|
119
123
|
5. **Task Completion Gate** — Before archiving, verify all tasks are complete:
|
|
120
|
-
- Read
|
|
124
|
+
- Read `${SPECRAILS_REPO_DIR:-.}/openspec/changes/<specName>/tasks.md`
|
|
121
125
|
- Search for any lines matching `- [ ]` (hyphen, space, open-bracket, space, close-bracket)
|
|
122
126
|
- **If any `- [ ]` lines are found**: BLOCK archive. List every incomplete task title. Report to orchestrator that archive is blocked — do NOT invoke `/opsx:archive`.
|
|
123
127
|
- **If no `- [ ]` lines remain** (all tasks are `- [x]`): gate passes — proceed to Step 6.
|
|
@@ -140,8 +144,8 @@ After running CI checks, also review for:
|
|
|
140
144
|
- "Delta specs: Sync now vs Archive without syncing?" → ALWAYS choose **Sync now** (canonical). NEVER skip the sync.
|
|
141
145
|
|
|
142
146
|
**3 — PROOF-OF-EXECUTION gate.** After the skill returns, verify on disk:
|
|
143
|
-
-
|
|
144
|
-
- the delta-spec changes are now present under
|
|
147
|
+
- `${SPECRAILS_REPO_DIR:-.}/openspec/changes/<specName>/` no longer exists (the change was moved), AND
|
|
148
|
+
- the delta-spec changes are now present under `${SPECRAILS_REPO_DIR:-.}/openspec/specs/` — open the affected `${SPECRAILS_REPO_DIR:-.}/openspec/specs/<capability>/spec.md` and confirm the change's added/modified requirements are there.
|
|
145
149
|
|
|
146
150
|
If the move happened but the specs were NOT synced (the classic *simulated-archive* symptom), recover canonically — **never hand-copy**:
|
|
147
151
|
- a. Invoke `Skill("opsx:sync", "<specName>")` (the official sync skill) and re-verify.
|
|
@@ -278,7 +282,7 @@ Score semantics:
|
|
|
278
282
|
|
|
279
283
|
### How to derive the change name
|
|
280
284
|
|
|
281
|
-
The change name is the kebab-case directory under
|
|
285
|
+
The change name is the kebab-case directory under `${SPECRAILS_REPO_DIR:-.}/openspec/changes/` that was active during this review. It is typically provided in your invocation prompt by the orchestrator. If not provided explicitly, find it by listing `${SPECRAILS_REPO_DIR:-.}/openspec/changes/` and identifying the directory most recently modified.
|
|
282
286
|
|
|
283
287
|
If the change name cannot be determined: write the score with `"change": "unknown"` and `"overall": 0`, and populate every `notes` field with an explanation of why the name could not be determined.
|
|
284
288
|
|
|
@@ -286,7 +290,7 @@ If the change name cannot be determined: write the score with `"change": "unknow
|
|
|
286
290
|
|
|
287
291
|
Write to:
|
|
288
292
|
```
|
|
289
|
-
openspec/changes/<name>/confidence-score.json
|
|
293
|
+
${SPECRAILS_REPO_DIR:-.}/openspec/changes/<name>/confidence-score.json
|
|
290
294
|
```
|
|
291
295
|
|
|
292
296
|
### Required fields
|