@openora/create 0.4.1-canary.35 → 0.4.1-canary.351
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/dist/.tsbuildinfo +1 -1
- package/dist/generated/versions.d.ts +11 -0
- package/dist/generated/versions.d.ts.map +1 -0
- package/dist/generated/versions.js +11 -0
- package/dist/generated/versions.js.map +1 -0
- package/dist/index.js +48 -93
- package/dist/index.js.map +1 -1
- package/dist/render.d.ts +15 -0
- package/dist/render.d.ts.map +1 -0
- package/dist/render.js +125 -0
- package/dist/render.js.map +1 -0
- package/package.json +8 -6
- package/template/README.md.tpl +12 -7
- package/template/__dot__claude/settings.json.tpl +22 -12
- package/template/__dot__env.example +10 -0
- package/template/__dot__gitignore +5 -0
- package/template/__dot__rulesync/commands/check.md +11 -10
- package/template/__dot__rulesync/commands/doctor.md.tpl +17 -0
- package/template/__dot__rulesync/commands/scaffold-module.md +3 -2
- package/template/__dot__rulesync/commands/scaffold-plugin.md +19 -3
- package/template/__dot__rulesync/commands/scaffold-route.md +1 -0
- package/template/__dot__rulesync/commands/start.md +2 -2
- package/template/__dot__rulesync/hooks/_shared.mjs +26 -13
- package/template/__dot__rulesync/hooks/guard-core.mjs.tpl +76 -28
- package/template/__dot__rulesync/hooks/guard-generated.mjs +15 -13
- package/template/__dot__rulesync/hooks/guard-mr.mjs +81 -0
- package/template/__dot__rulesync/hooks/guard-subagent.mjs +22 -6
- package/template/__dot__rulesync/hooks/post-edit.mjs.tpl +116 -0
- package/template/__dot__rulesync/hooks.json +27 -4
- package/template/__dot__rulesync/rules/conventions.md +99 -119
- package/template/__dot__rulesync/rules/db-conventions.md +6 -72
- package/template/__dot__rulesync/rules/e2e-conventions.md +33 -7
- package/template/__dot__rulesync/rules/frontend-conventions.md.tpl +35 -0
- package/template/__dot__rulesync/rules/oss-boundaries.md.tpl +78 -0
- package/template/__dot__rulesync/rules/overview.md +18 -82
- package/template/__dot__rulesync/skills/create-plugin/{SKILL.md → SKILL.md.tpl} +12 -23
- package/template/__dot__rulesync/skills/create-pr/SKILL.md.tpl +38 -0
- package/template/__dot__rulesync/skills/create-task/{SKILL.md → SKILL.md.tpl} +14 -22
- package/template/__dot__rulesync/skills/create-ui-module/{SKILL.md → SKILL.md.tpl} +12 -10
- package/template/__dot__rulesync/skills/enhance-prompt/{SKILL.md → SKILL.md.tpl} +4 -4
- package/template/__dot__rulesync/skills/handoff/SKILL.md +95 -0
- package/template/__dot__rulesync/skills/review/SKILL.md.tpl +277 -0
- package/template/__dot__rulesync/skills/ship/SKILL.md.tpl +157 -0
- package/template/__dot__rulesync/skills/ship/handoff.md.tpl +48 -0
- package/template/__dot__rulesync/skills/ship/watch-pr.mjs +172 -0
- package/template/__dot__rulesync/skills/ui-verify/SKILL.md.tpl +120 -0
- package/template/__dot__rulesync/skills/ui-verify/layout-check.check.sh +58 -0
- package/template/__dot__rulesync/skills/ui-verify/layout-check.js +163 -0
- package/template/__dot__rulesync/subagents/builder.md.tpl +115 -0
- package/template/__dot__rulesync/subagents/compliance-reviewer.md +78 -0
- package/template/__dot__rulesync/subagents/debugger.md.tpl +86 -0
- package/template/__dot__rulesync/subagents/deployer.md.tpl +54 -0
- package/template/__dot__rulesync/subagents/expert.md +28 -34
- package/template/__dot__rulesync/subagents/planner.md.tpl +39 -0
- package/template/__dot__rulesync/subagents/quality-reviewer.md.tpl +112 -0
- package/template/__dot__rulesync/subagents/security-reviewer.md.tpl +85 -0
- package/template/__dot__rulesync/subagents/tester.md.tpl +102 -0
- package/template/__dot__rulesync/sync.json.tpl +26 -0
- package/template/apps/api/package.json.tpl +2 -2
- package/template/apps/api/src/extensions.config.ts.tpl +12 -0
- package/template/apps/api/src/main.ts.tpl +1 -4
- package/template/apps/api/src/seed.ts.tpl +8 -1
- package/template/docker-compose.yml +13 -0
- package/template/docs/agents/cross-repo.md.tpl +37 -0
- package/template/docs/agents/forge.md.tpl +54 -0
- package/template/docs/agents/issue-tracker.md.tpl +42 -0
- package/template/docs/standards/comments.md +16 -0
- package/template/docs/standards/database.md +77 -0
- package/template/docs/standards/enforcement.md +12 -0
- package/template/docs/standards/errors.md +30 -0
- package/template/docs/standards/frontend.md +60 -0
- package/template/docs/standards/functions.md +38 -0
- package/template/docs/standards/git-delivery.md +20 -0
- package/template/docs/standards/module-structure.md +37 -0
- package/template/docs/standards/testing.md +88 -0
- package/template/docs/standards/types.md +31 -0
- package/template/package.json.tpl +15 -9
- package/template/rulesync.jsonc +3 -3
- package/template/tools/oss-worktree.mjs.tpl +105 -0
- package/template/tools/review-precheck.mjs +531 -0
- package/template/tools/sync-agents.mjs +257 -0
- package/dist/generated/core-version.d.ts +0 -2
- package/dist/generated/core-version.d.ts.map +0 -1
- package/dist/generated/core-version.js +0 -2
- package/dist/generated/core-version.js.map +0 -1
- package/template/__dot__rulesync/commands/doctor.md +0 -16
- package/template/__dot__rulesync/hooks/post-edit.mjs +0 -57
- package/template/__dot__rulesync/rules/oss-boundaries.md +0 -31
- package/template/__dot__rulesync/skills/add-feature/SKILL.md +0 -113
- package/template/__dot__rulesync/skills/add-feature/handoff.md +0 -58
- package/template/__dot__rulesync/skills/create-pr/SKILL.md +0 -55
- package/template/__dot__rulesync/skills/review/SKILL.md +0 -110
- package/template/__dot__rulesync/subagents/builder.md +0 -93
- package/template/__dot__rulesync/subagents/debugger.md +0 -82
- package/template/__dot__rulesync/subagents/deployer.md +0 -66
- package/template/__dot__rulesync/subagents/qa.md +0 -88
- package/template/__dot__rulesync/subagents/quality-reviewer.md +0 -67
- package/template/__dot__rulesync/subagents/security-reviewer.md +0 -54
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Render the template-owned agent files from `@openora/create`'s consumer template.
|
|
3
|
+
//
|
|
4
|
+
// Source of truth: node_modules/@openora/create/template at the pinned @openora/* version
|
|
5
|
+
// (with `pnpm link:oss`, ../openora/packages/create-openora's own tools/templates/consumer).
|
|
6
|
+
// Runs on `pnpm install` via `prepare`, so these files are generated artifacts: they are
|
|
7
|
+
// listed in the managed .gitignore block below and never committed. Only this repo's own
|
|
8
|
+
// rules and skills - and the `consumerOwned` overrides in .rulesync/sync.json - are tracked.
|
|
9
|
+
//
|
|
10
|
+
// To change a synced file, edit openora's tools/templates/consumer and take the next bump.
|
|
11
|
+
import {
|
|
12
|
+
existsSync,
|
|
13
|
+
mkdirSync,
|
|
14
|
+
readdirSync,
|
|
15
|
+
readFileSync,
|
|
16
|
+
realpathSync,
|
|
17
|
+
rmdirSync,
|
|
18
|
+
statSync,
|
|
19
|
+
unlinkSync,
|
|
20
|
+
writeFileSync,
|
|
21
|
+
} from 'node:fs';
|
|
22
|
+
import { execFileSync } from 'node:child_process';
|
|
23
|
+
import { createRequire } from 'node:module';
|
|
24
|
+
import { dirname, join, relative, sep } from 'node:path';
|
|
25
|
+
|
|
26
|
+
const ROOT = process.cwd();
|
|
27
|
+
const SYNCED_ROOTS = [
|
|
28
|
+
'.rulesync/',
|
|
29
|
+
'docs/standards/',
|
|
30
|
+
'docs/agents/',
|
|
31
|
+
'tools/sync-agents.mjs',
|
|
32
|
+
'tools/oss-worktree.mjs',
|
|
33
|
+
'tools/review-precheck.mjs',
|
|
34
|
+
];
|
|
35
|
+
// This script bootstraps `prepare`, so it must stay tracked: ignoring it would leave a
|
|
36
|
+
// fresh clone with no way to run the install that would have rendered it.
|
|
37
|
+
// Tracked on purpose: `prepare` runs the script, and the Claude hooks in .claude/settings.json
|
|
38
|
+
// import _shared.mjs, so both must exist in a fresh clone before the first install.
|
|
39
|
+
const NEVER_IGNORED = new Set(['tools/sync-agents.mjs', '.rulesync/hooks/_shared.mjs']);
|
|
40
|
+
const SKIPPED_BASENAMES = new Set();
|
|
41
|
+
// Dropped before the manifest below existed. The first install after the bump runs the previous,
|
|
42
|
+
// tracked copy of this script, which overwrites itself and the .gitignore block but deletes
|
|
43
|
+
// nothing, so the next run has no record of these.
|
|
44
|
+
const RETIRED = [
|
|
45
|
+
'.rulesync/skills/add-feature/SKILL.md',
|
|
46
|
+
'.rulesync/skills/add-feature/handoff.md',
|
|
47
|
+
'.rulesync/subagents/qa.md',
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
const die = (message) => {
|
|
51
|
+
console.error(`sync:agents - ${message}`);
|
|
52
|
+
process.exit(1);
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const config = (() => {
|
|
56
|
+
const path = join(ROOT, '.rulesync/sync.json');
|
|
57
|
+
if (!existsSync(path)) {
|
|
58
|
+
die(`${path} is missing - it holds this repo's template variables and consumerOwned list.`);
|
|
59
|
+
}
|
|
60
|
+
try {
|
|
61
|
+
return JSON.parse(readFileSync(path, 'utf8'));
|
|
62
|
+
} catch (err) {
|
|
63
|
+
return die(`${path} is not valid JSON: ${err.message}`);
|
|
64
|
+
}
|
|
65
|
+
})();
|
|
66
|
+
const vars = config.vars ?? {};
|
|
67
|
+
const consumerOwned = new Set(config.consumerOwned ?? []);
|
|
68
|
+
|
|
69
|
+
// The published package ships `template/`; a linked source checkout (pnpm link:oss, CI's
|
|
70
|
+
// unbuilt clone) only has the source at tools/templates/consumer - read that directly.
|
|
71
|
+
const templateRoot = (() => {
|
|
72
|
+
let pkgDir;
|
|
73
|
+
try {
|
|
74
|
+
pkgDir = dirname(createRequire(join(ROOT, '/')).resolve('@openora/create/package.json'));
|
|
75
|
+
} catch {
|
|
76
|
+
return die('@openora/create is not installed - add it as a devDependency and reinstall.');
|
|
77
|
+
}
|
|
78
|
+
const candidates = [
|
|
79
|
+
join(pkgDir, 'template'),
|
|
80
|
+
join(pkgDir, '..', '..', 'tools', 'templates', 'consumer'),
|
|
81
|
+
];
|
|
82
|
+
const found = candidates.find((dir) => existsSync(dir));
|
|
83
|
+
if (!found) {
|
|
84
|
+
return die(`consumer template not found at ${candidates.join(' or ')}`);
|
|
85
|
+
}
|
|
86
|
+
return found;
|
|
87
|
+
})();
|
|
88
|
+
|
|
89
|
+
const walk = (dir) =>
|
|
90
|
+
readdirSync(dir).flatMap((entry) => {
|
|
91
|
+
const full = join(dir, entry);
|
|
92
|
+
return statSync(full).isDirectory() ? walk(full) : [full];
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
const undot = (segment) => (segment.startsWith('__dot__') ? `.${segment.slice(7)}` : segment);
|
|
96
|
+
|
|
97
|
+
const gitTracked = (paths) => {
|
|
98
|
+
try {
|
|
99
|
+
return execFileSync('git', ['ls-files', '-z', '--', ...paths], {
|
|
100
|
+
cwd: ROOT,
|
|
101
|
+
encoding: 'utf8',
|
|
102
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
103
|
+
})
|
|
104
|
+
.split('\0')
|
|
105
|
+
.filter(Boolean);
|
|
106
|
+
} catch {
|
|
107
|
+
return [];
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const substitute = (content) =>
|
|
112
|
+
content.replace(/\{\{(\w+)\}\}/g, (match, key) => vars[key] ?? match);
|
|
113
|
+
|
|
114
|
+
const render = (file) => {
|
|
115
|
+
const rel = relative(templateRoot, file).split(sep).map(undot).join('/');
|
|
116
|
+
const isTpl = rel.endsWith('.tpl');
|
|
117
|
+
const raw = readFileSync(file, 'utf8');
|
|
118
|
+
return { path: isTpl ? rel.slice(0, -4) : rel, content: isTpl ? substitute(raw) : raw };
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const rendered = walk(templateRoot)
|
|
122
|
+
.filter((file) => !SKIPPED_BASENAMES.has(file.split(sep).at(-1)))
|
|
123
|
+
.map(render)
|
|
124
|
+
.filter(({ path }) => SYNCED_ROOTS.some((root) => path.startsWith(root)))
|
|
125
|
+
.filter(({ path }) => !consumerOwned.has(path));
|
|
126
|
+
|
|
127
|
+
// A new template variable must not brick `pnpm install` (which is also how the bump MR that
|
|
128
|
+
// would add it gets built) - leave the placeholder in place and say what to add.
|
|
129
|
+
const unresolved = rendered.filter(({ content }) => /\{\{\w+\}\}/.test(content));
|
|
130
|
+
if (unresolved.length > 0) {
|
|
131
|
+
console.warn(
|
|
132
|
+
'sync:agents - unresolved template variables, add them to .rulesync/sync.json vars:',
|
|
133
|
+
);
|
|
134
|
+
for (const { path, content } of unresolved) {
|
|
135
|
+
console.warn(` ${path}: ${[...new Set(content.match(/\{\{\w+\}\}/g))].join(' ')}`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Tracking a path is how this repo says "mine": .gitignore does not apply to it, so writing
|
|
140
|
+
// would replace the committed content and leave a permanently dirty file. Skip those and
|
|
141
|
+
// name them - opting one into being generated is a deliberate `git rm --cached`.
|
|
142
|
+
const trackedPaths = new Set(gitTracked(rendered.map(({ path }) => path)));
|
|
143
|
+
const owned = rendered.filter(({ path }) => trackedPaths.has(path) && !NEVER_IGNORED.has(path));
|
|
144
|
+
|
|
145
|
+
for (const { path, content } of rendered) {
|
|
146
|
+
if (trackedPaths.has(path) && !NEVER_IGNORED.has(path)) {
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
const target = join(ROOT, path);
|
|
150
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
151
|
+
if (!existsSync(target) || readFileSync(target, 'utf8') !== content) {
|
|
152
|
+
writeFileSync(target, content);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Git must ignore exactly what this script writes, so a template that gains or loses a
|
|
157
|
+
// file cannot leave an untracked stray behind. The block is rewritten in place; anything
|
|
158
|
+
// outside the markers is left alone.
|
|
159
|
+
const BEGIN = '# BEGIN synced-agents (generated by tools/sync-agents.mjs - do not edit)';
|
|
160
|
+
const END = '# END synced-agents';
|
|
161
|
+
const gitignore = join(ROOT, '.gitignore');
|
|
162
|
+
// A tracked path is never ignored by git anyway - listing it would only mislead.
|
|
163
|
+
const ignored = rendered.filter(({ path }) => !NEVER_IGNORED.has(path) && !trackedPaths.has(path));
|
|
164
|
+
const block = [BEGIN, ...ignored.map(({ path }) => `/${path}`).sort(), END].join('\n');
|
|
165
|
+
const current = existsSync(gitignore) ? readFileSync(gitignore, 'utf8') : '';
|
|
166
|
+
const start = current.indexOf(BEGIN);
|
|
167
|
+
// Search for END after BEGIN: a stray END earlier in the file (hand-edit, bad merge) would
|
|
168
|
+
// otherwise splice at a negative offset and duplicate the whole block.
|
|
169
|
+
const end = start === -1 ? -1 : current.indexOf(END, start + BEGIN.length);
|
|
170
|
+
if (start !== -1 && end === -1) {
|
|
171
|
+
die(`${gitignore} has a ${BEGIN} marker with no matching ${END} - repair or delete the block.`);
|
|
172
|
+
}
|
|
173
|
+
// A file the template dropped or renamed is still on disk from the last render, untracked and
|
|
174
|
+
// no longer ignored: delete what fell out of it. The old block lists what was rendered then,
|
|
175
|
+
// but after a branch switch the committed block already names the new set, so the manifest
|
|
176
|
+
// (outside git, per checkout) remembers what this checkout actually has on disk.
|
|
177
|
+
const manifest = join(ROOT, 'node_modules/.cache/sync-agents.json');
|
|
178
|
+
const lastRendered = (() => {
|
|
179
|
+
try {
|
|
180
|
+
const paths = JSON.parse(readFileSync(manifest, 'utf8'));
|
|
181
|
+
return Array.isArray(paths) ? paths.filter((path) => typeof path === 'string') : [];
|
|
182
|
+
} catch {
|
|
183
|
+
return [];
|
|
184
|
+
}
|
|
185
|
+
})();
|
|
186
|
+
const lastBlock =
|
|
187
|
+
start === -1
|
|
188
|
+
? []
|
|
189
|
+
: current
|
|
190
|
+
.slice(start + BEGIN.length, end)
|
|
191
|
+
.split('\n')
|
|
192
|
+
.filter((line) => line.startsWith('/'))
|
|
193
|
+
.map((line) => line.slice(1));
|
|
194
|
+
// The block and the manifest are plain text anyone can edit: a symlinked directory on the way
|
|
195
|
+
// would point the delete outside the checkout, so every parent must resolve to itself.
|
|
196
|
+
const realRoot = realpathSync(ROOT);
|
|
197
|
+
const parentIsReal = (path) => {
|
|
198
|
+
try {
|
|
199
|
+
return realpathSync(dirname(join(ROOT, path))) === join(realRoot, dirname(path));
|
|
200
|
+
} catch {
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
const stale = [...new Set([...lastBlock, ...lastRendered, ...RETIRED])]
|
|
205
|
+
.filter((path) => SYNCED_ROOTS.some((root) => path.startsWith(root)) && !path.includes('..'))
|
|
206
|
+
.filter((path) => !ignored.some((file) => file.path === path) && !consumerOwned.has(path))
|
|
207
|
+
.filter(parentIsReal);
|
|
208
|
+
// Never one this repo has since started tracking. (No paths: ls-files would list them all.)
|
|
209
|
+
const staleTracked = new Set(stale.length > 0 ? gitTracked(stale) : []);
|
|
210
|
+
for (const path of stale.filter((path) => !staleTracked.has(path))) {
|
|
211
|
+
const target = join(ROOT, path);
|
|
212
|
+
if (existsSync(target)) {
|
|
213
|
+
unlinkSync(target);
|
|
214
|
+
for (
|
|
215
|
+
let dir = dirname(target);
|
|
216
|
+
dir !== ROOT && readdirSync(dir).length === 0;
|
|
217
|
+
dir = dirname(dir)
|
|
218
|
+
) {
|
|
219
|
+
rmdirSync(dir);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
const updated =
|
|
224
|
+
start === -1
|
|
225
|
+
? `${current.trimEnd()}\n\n${block}\n`.trimStart()
|
|
226
|
+
: current.slice(0, start) + block + current.slice(end + END.length);
|
|
227
|
+
if (updated !== current) {
|
|
228
|
+
writeFileSync(gitignore, updated);
|
|
229
|
+
}
|
|
230
|
+
mkdirSync(dirname(manifest), { recursive: true });
|
|
231
|
+
writeFileSync(manifest, JSON.stringify(ignored.map(({ path }) => path)));
|
|
232
|
+
|
|
233
|
+
console.log(`sync:agents - ${rendered.length - owned.length} template-owned file(s) rendered.`);
|
|
234
|
+
if (owned.length > 0) {
|
|
235
|
+
console.warn(
|
|
236
|
+
` ${owned.length} kept as-is because this repo tracks them: ${owned.map(({ path }) => path).join(', ')}\n` +
|
|
237
|
+
` To let the template own one, untrack it: git rm --cached <path>`,
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Both files are tracked by every consumer, so a template bump cannot wire the verify gate in.
|
|
242
|
+
const missingWiring = [
|
|
243
|
+
['.claude/settings.json', 'guard-mr.mjs', 'a PreToolUse "Bash" hook running guard-mr.mjs'],
|
|
244
|
+
[
|
|
245
|
+
'package.json',
|
|
246
|
+
'guard-mr.mjs --stamp',
|
|
247
|
+
'end "verify" with `&& node .rulesync/hooks/guard-mr.mjs --stamp`',
|
|
248
|
+
],
|
|
249
|
+
].filter(([path, marker]) => {
|
|
250
|
+
const target = join(ROOT, path);
|
|
251
|
+
return existsSync(target) && !readFileSync(target, 'utf8').includes(marker);
|
|
252
|
+
});
|
|
253
|
+
for (const [path, , fix] of missingWiring) {
|
|
254
|
+
console.warn(
|
|
255
|
+
`sync:agents - ${path} is missing the verify gate: ${fix} (see the template's copy).`,
|
|
256
|
+
);
|
|
257
|
+
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"core-version.d.ts","sourceRoot":"","sources":["../../src/generated/core-version.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,YAAY,qBAAqB,CAAC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"core-version.js","sourceRoot":"","sources":["../../src/generated/core-version.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,YAAY,GAAG,kBAAkB,CAAC"}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
targets:
|
|
3
|
-
- '*'
|
|
4
|
-
description: Diagnose the local dev environment - linked ../oss, node, postgres, ports, caches. Read-only; reports findings + the fix command for each.
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
Run these checks and report a pass/fail line per item with the fix command for failures. Read-only - never apply fixes without being asked.
|
|
8
|
-
|
|
9
|
-
1. **Node version**: `node -v` matches `.nvmrc` (26). Fix: `nvm use`.
|
|
10
|
-
2. **Core resolves**: `node --input-type=module -e "import {createRequire} from 'node:module'; console.log(createRequire(process.cwd()+'/').resolve('@openora/core/react'))"` prints a path. Fix: `pnpm install`.
|
|
11
|
-
3. **Database**: postgres reachable on :5432 (`nc -z localhost 5432`). Fix: `dev:infra` MCP tool (server `oss`) or the compose stack.
|
|
12
|
-
4. **Services**: ports 3001 (api), 3000 (web), 3002 (backoffice) - report which are up. Fix: `pnpm dev`.
|
|
13
|
-
5. **Stale bundler cache**: if a build error persists after a fix, `rm -rf apps/*/.next` and rebuild (Turbopack caches across the link boundary).
|
|
14
|
-
6. **Generated agent files**: `pnpm gen:agents` runs clean (regenerates from `.rulesync/`).
|
|
15
|
-
|
|
16
|
-
If everything passes but a build still fails, hand off to the `debugger` agent with the verbatim error - it owns the deeper resolution table (turbopack.root, symlinked tsconfig, externalDir).
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { execSync } from 'node:child_process';
|
|
3
|
-
import { readFileSync } from 'node:fs';
|
|
4
|
-
import { join, isAbsolute, relative } from 'node:path';
|
|
5
|
-
import { extractFilePath, readPayload } from './_shared.mjs';
|
|
6
|
-
|
|
7
|
-
const CAP_LINES = 40;
|
|
8
|
-
const CAP_CHARS = 2000;
|
|
9
|
-
|
|
10
|
-
const filePath = extractFilePath(readPayload());
|
|
11
|
-
if (!filePath) process.exit(0);
|
|
12
|
-
if (!/\.(ts|tsx)$/.test(filePath) || /\.d\.ts$/.test(filePath)) process.exit(0);
|
|
13
|
-
if (filePath.includes('/templates/') || filePath.includes('/generated/')) process.exit(0);
|
|
14
|
-
|
|
15
|
-
// Lint-fix (best effort - never block on the linter).
|
|
16
|
-
try {
|
|
17
|
-
execSync(`pnpm exec oxlint --fix "${filePath}"`, { stdio: 'pipe' });
|
|
18
|
-
} catch {
|
|
19
|
-
/* oxlint unavailable or errored - fall through to typecheck */
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function packageNameFor(fp) {
|
|
23
|
-
const m = fp.match(/(.*?\/(?:apps\/[^/]+|packages\/[^/]+\/[^/]+))\//);
|
|
24
|
-
if (!m) return null;
|
|
25
|
-
try {
|
|
26
|
-
const pkg = JSON.parse(readFileSync(join(m[1], 'package.json'), 'utf8'));
|
|
27
|
-
if (!pkg.name || !pkg.scripts?.typecheck) return null;
|
|
28
|
-
return pkg.name;
|
|
29
|
-
} catch {
|
|
30
|
-
return null;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const abs = isAbsolute(filePath) ? filePath : join(process.cwd(), filePath);
|
|
35
|
-
const owner = packageNameFor(abs);
|
|
36
|
-
if (!owner) process.exit(0);
|
|
37
|
-
|
|
38
|
-
function cap(text) {
|
|
39
|
-
const out = text.split('\n').slice(0, CAP_LINES).join('\n').slice(0, CAP_CHARS);
|
|
40
|
-
return out.length < text.length ? `${out}\n... (truncated)` : out;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
try {
|
|
44
|
-
execSync(`pnpm --filter "${owner}" typecheck`, { stdio: 'pipe' });
|
|
45
|
-
process.exit(0);
|
|
46
|
-
} catch (e) {
|
|
47
|
-
const output = (e.stdout?.toString() ?? '') + (e.stderr?.toString() ?? '');
|
|
48
|
-
const basename =
|
|
49
|
-
(isAbsolute(filePath) ? relative(process.cwd(), filePath) : filePath).split('/').pop() ?? '';
|
|
50
|
-
if (basename && output.includes(basename)) {
|
|
51
|
-
process.stderr.write(
|
|
52
|
-
`Typecheck failed for ${owner} after editing ${basename}:\n${cap(output)}`,
|
|
53
|
-
);
|
|
54
|
-
process.exit(2);
|
|
55
|
-
}
|
|
56
|
-
process.exit(0);
|
|
57
|
-
}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
root: false
|
|
3
|
-
targets:
|
|
4
|
-
- '*'
|
|
5
|
-
globs:
|
|
6
|
-
- '**/*'
|
|
7
|
-
description: OSS core is read-only; enforced import/module boundaries.
|
|
8
|
-
---
|
|
9
|
-
|
|
10
|
-
# OSS core + import boundaries
|
|
11
|
-
|
|
12
|
-
## Never modify OSS core
|
|
13
|
-
|
|
14
|
-
`@openora/*` is a third-party dependency - read it for reference, never write to it.
|
|
15
|
-
|
|
16
|
-
- Do NOT edit `node_modules/**` or a linked OSS checkout. Those paths are write-denied in `.claude/settings.json`; don't route around it with `sed`, redirection, or scripts. A patched dependency is lost on reinstall and diverges from the published package.
|
|
17
|
-
- Extend from the OUTSIDE only: overlay plugins, adapter rebindings, UI plugins, config.
|
|
18
|
-
- If something can only be fixed in core, STOP and report it upstream (problem, expected behavior, likely location).
|
|
19
|
-
|
|
20
|
-
## Import boundaries (enforced)
|
|
21
|
-
|
|
22
|
-
Enforced by `pnpm check:lint` (oxlint, per-edit), `pnpm check:boundaries` (dependency-cruiser, whole graph), the pre-commit hook, CI, and the agent PostToolUse hook. Fix the import, never work around a violation.
|
|
23
|
-
|
|
24
|
-
- No deep OSS imports: `@openora/*/src/*` or `/dist/*` - import only the published entrypoint or subpath export.
|
|
25
|
-
- No deep imports into your own shared packages - only the barrel/index entrypoint.
|
|
26
|
-
- No app-to-app imports (`apps/api` <-> `apps/web` <-> `apps/backoffice`) - extract shared code to `packages/*`.
|
|
27
|
-
- No cross-module imports inside an app - go through the module barrel, a query invalidation, or a domain event.
|
|
28
|
-
- No overlay-to-overlay imports - couple via a command port, a domain event, or a shared contract.
|
|
29
|
-
- No import cycles.
|
|
30
|
-
|
|
31
|
-
`pnpm check:boundaries:graph` renders the graph (needs Graphviz).
|
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: add-feature
|
|
3
|
-
|
|
4
|
-
description: >
|
|
5
|
-
Deliver a feature end-to-end in this consumer repo. Aggregates context (Jira + Confluence + Slack +
|
|
6
|
-
Google Drive + Notion + local docs + past sessions + codebase), produces an approved plan, then drives
|
|
7
|
-
delivery by calling sibling skills - create-plugin (build), review, create-pr (MR) -
|
|
8
|
-
and create-task for ticket hygiene. Transitions Jira (no comments) and drafts a one-line Slack
|
|
9
|
-
notice. Use on "add feature", "plan <KEY>-XXX", "deliver <KEY>-XXX", or /add-feature [<KEY>-XXX].
|
|
10
|
-
Read-only until the plan is approved; never pushes, transitions Jira, or sends Slack without OK.
|
|
11
|
-
---
|
|
12
|
-
|
|
13
|
-
# add-feature
|
|
14
|
-
|
|
15
|
-
Feature-delivery orchestrator for this consumer repo: one Jira key in, a delivered MR +
|
|
16
|
-
updated ticket + drafted Slack notice out. You orchestrate and call sibling skills - you do not
|
|
17
|
-
re-implement their work. The platform-core twin is the `/add-feature` skill in the platform OSS repo.
|
|
18
|
-
|
|
19
|
-
## Coordinates
|
|
20
|
-
|
|
21
|
-
- Jira: the **Atlassian** MCP, cloudId `<your-jira-cloud-id>`,
|
|
22
|
-
project `<your-project-key>` (ticket keys look like `<KEY>-XXX`). Pass `contentFormat` +
|
|
23
|
-
`responseContentFormat: "markdown"`.
|
|
24
|
-
- GitLab: `<your-gitlab-project>`, MR target `dev`, `glab` CLI.
|
|
25
|
-
- Slack: `<your-team-channel>`, draft only.
|
|
26
|
-
- Repo: `apps/api` (Hono entry + extensions) consumes `@openora/*` upstream. This is a headless
|
|
27
|
-
API consumer; build your frontend in its own repo and consume the API over HTTP.
|
|
28
|
-
- **Hard rule:** the linked OSS checkout is read-only (guard-core hook + permission deny). Extend from
|
|
29
|
-
the outside; core changes hand off - see `handoff.md`.
|
|
30
|
-
|
|
31
|
-
## The contract
|
|
32
|
-
|
|
33
|
-
- **Read-only until the Step 3 plan is approved.** No edits, commits, pushes, Jira writes, or Slack
|
|
34
|
-
sends before sign-off.
|
|
35
|
-
- Reuse sibling skills, don't reinvent: **create-task** (ticket format), **create-plugin** (build an
|
|
36
|
-
overlay), **review** (review), **create-pr** (MR). Delegate code to subagents.
|
|
37
|
-
|
|
38
|
-
## Steps
|
|
39
|
-
|
|
40
|
-
### 1. Resolve input + enhance the ask
|
|
41
|
-
|
|
42
|
-
`<KEY>-XXX` from `$ARGUMENTS`; if absent, ask. Echo it back. Run the `enhance-prompt` pre-step on the ask before gathering context, so Step 2 pulls only what's relevant and Step 3 plans against a clear brief.
|
|
43
|
-
|
|
44
|
-
### 2. Gather context in parallel (read-only)
|
|
45
|
-
|
|
46
|
-
Run together; skip any source that returns nothing. Read `handoff.md` only on core-change signals.
|
|
47
|
-
|
|
48
|
-
| Source | How |
|
|
49
|
-
| ------------- | ----------------------------------------------------------------------------------------------- |
|
|
50
|
-
| Jira | the **Atlassian** MCP - read `<KEY>-XXX`: description, AC, comments, parent epic, linked issues |
|
|
51
|
-
| Confluence | the **Atlassian** (Confluence) MCP - search the space + read pages |
|
|
52
|
-
| Slack | the **Slack** MCP - search public/private, read threads, read canvases |
|
|
53
|
-
| Google Drive | the **Google Drive** MCP - PRDs, specs |
|
|
54
|
-
| Notion | `ntn` CLI per `notion-memory` skill - prior decisions, lessons |
|
|
55
|
-
| Local docs | the OSS checkout's `docs` (ADRs, `architecture.md`, `catalog.json`), repo READMEs, `CLAUDE.md` |
|
|
56
|
-
| Past sessions | grep `~/.claude/projects/**` and `~/.claude/plans` for the ticket key |
|
|
57
|
-
| Codebase | `oss` MCP (read-only) + Explore - map touchpoints in `apps/api` |
|
|
58
|
-
|
|
59
|
-
### 3. Plan + classify (the gate)
|
|
60
|
-
|
|
61
|
-
Synthesize into a plan and present it. Do NOT edit yet.
|
|
62
|
-
|
|
63
|
-
- **Goal** (1-2 lines) + **Acceptance criteria** (observable, testable).
|
|
64
|
-
- **Decisions found** - each with source (who/where/date), so they aren't relitigated.
|
|
65
|
-
- **Open questions** - ask before proceeding if any blocks design.
|
|
66
|
-
- **Implementation breakdown** - tasks mapped to files/packages + the owning subagent. Classify each:
|
|
67
|
-
- **downstream** -> overlay plugin / adapter swap / UI provider / config (build via `create-plugin`).
|
|
68
|
-
- **OSS-core** -> only fixable in `@openora/*`. Flag it; triggers `handoff.md`.
|
|
69
|
-
- **Risks / dependencies** - external services, OSS handoff, data/migrations.
|
|
70
|
-
|
|
71
|
-
Require explicit approval. Treat as plan mode even if the harness isn't.
|
|
72
|
-
|
|
73
|
-
### 4. Build (delegate)
|
|
74
|
-
|
|
75
|
-
After approval, for **downstream** work: run the **create-plugin** skill for each overlay/adapter/
|
|
76
|
-
page slice - it scaffolds, wires `extensions.config.ts`, and enforces boundaries + audit + db rules.
|
|
77
|
-
The owning subagent (`builder`) also writes unit + integration tests as part of the deliverable.
|
|
78
|
-
`deployer` only if infra changes; `debugger` on demand for build/runtime failures.
|
|
79
|
-
|
|
80
|
-
For **OSS-core** items: read `handoff.md`, write the work-order, STOP that slice, continue the rest.
|
|
81
|
-
When implementation starts, transition Jira to In Progress (Step 7 - confirm first).
|
|
82
|
-
|
|
83
|
-
### 5. Review + tests
|
|
84
|
-
|
|
85
|
-
- Run **review** on the change set; loop `[BLOCK]`/`[WARN]` fixes back through `builder`.
|
|
86
|
-
- Run `/check` (typecheck + lint). Don't proceed on red.
|
|
87
|
-
- Derive an e2e checklist from the AC (happy path, edge cases, authz negatives, error states), then
|
|
88
|
-
`qa`: write/run the E2E specs, drive `chrome-devtools` on failure.
|
|
89
|
-
|
|
90
|
-
### 6. Open the MR
|
|
91
|
-
|
|
92
|
-
Run **create-pr**: it commits (`feat(<KEY>-XXX): ...`), reports the SHA, asks for "yes push", pushes,
|
|
93
|
-
and `glab mr create`s targeting `dev` with the CODEOWNERS for the changed paths as reviewers. Don't
|
|
94
|
-
bypass its push-consent gate.
|
|
95
|
-
|
|
96
|
-
### 7. Jira status transition (NOT comments)
|
|
97
|
-
|
|
98
|
-
Use the **Atlassian** MCP - fetch the transitions -> show current status + options -> **confirm** ->
|
|
99
|
-
apply the matching one (In Progress when build starts, In Review when the MR opens). **No MR-link or status comments.**
|
|
100
|
-
|
|
101
|
-
### 8. Draft Slack notice (one line)
|
|
102
|
-
|
|
103
|
-
Use the **Slack** MCP to draft a message to `<your-team-channel>`: a single line - emoji + PR/task name
|
|
104
|
-
as a link (e.g. `👉 <feature> - MR !NN`). **Draft only**, never direct-send.
|
|
105
|
-
|
|
106
|
-
## Rules
|
|
107
|
-
|
|
108
|
-
- Read-only until the Step 3 plan is approved.
|
|
109
|
-
- Never push without an explicit per-action "yes push" (inherited from `create-pr`).
|
|
110
|
-
- Never transition Jira without confirming; show status + options first. No Jira comments.
|
|
111
|
-
- Slack is a one-line draft, never direct-send.
|
|
112
|
-
- Never edit the linked OSS checkout; hand off via `handoff.md`. Prefer overlay/plugin/adapter/config.
|
|
113
|
-
- One MR = one concern. Split unrelated work.
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
# OSS core handoff
|
|
2
|
-
|
|
3
|
-
When an `add-feature` work item can only be fixed in `@openora/*` core, this consumer repo cannot
|
|
4
|
-
edit it (the `guard-core.mjs` hook and `permissions.deny` block the linked OSS checkout). Hand off to
|
|
5
|
-
the platform-core twin skill instead of fighting the guard.
|
|
6
|
-
|
|
7
|
-
## When to hand off
|
|
8
|
-
|
|
9
|
-
- The fix requires changing a contract, schema, service, router, or platform seam in `@openora/*`.
|
|
10
|
-
- An overlay plugin / adapter swap / UI provider override / config change genuinely cannot
|
|
11
|
-
express the behavior.
|
|
12
|
-
- Default bias: try to extend from the outside first. Only hand off when you've confirmed the
|
|
13
|
-
outside-in path doesn't exist.
|
|
14
|
-
|
|
15
|
-
If you're unsure, say so in the plan (Step 3) and let the user decide before writing the order.
|
|
16
|
-
|
|
17
|
-
## Work-order template
|
|
18
|
-
|
|
19
|
-
Write to `~/.claude/plans/<ticket-key>-oss.md` so both repos and future sessions can read it:
|
|
20
|
-
|
|
21
|
-
```markdown
|
|
22
|
-
# OSS work order - <ticket-key>: <title>
|
|
23
|
-
|
|
24
|
-
## Goal
|
|
25
|
-
|
|
26
|
-
<what core must do, 1-2 lines>
|
|
27
|
-
|
|
28
|
-
## Unblocks
|
|
29
|
-
|
|
30
|
-
consumer feature <ticket-key> - <which downstream slice depends on this>
|
|
31
|
-
|
|
32
|
-
## Core surface to change
|
|
33
|
-
|
|
34
|
-
- <package / module / contract / adapter token, as specific as possible>
|
|
35
|
-
|
|
36
|
-
## Contract / schema impact
|
|
37
|
-
|
|
38
|
-
- <new or changed oRPC contracts, Drizzle tables, events; or "none">
|
|
39
|
-
|
|
40
|
-
## Acceptance
|
|
41
|
-
|
|
42
|
-
- <observable outcomes that prove the core change is correct>
|
|
43
|
-
|
|
44
|
-
## Dependency
|
|
45
|
-
|
|
46
|
-
consumer MR <url-or-TBD> stays in draft until this merges and a new @openora/core/\* version is consumed.
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
## Protocol
|
|
50
|
-
|
|
51
|
-
1. The consumer repo writes the work-order and **stops** that slice (keep delivering independent
|
|
52
|
-
downstream slices meanwhile).
|
|
53
|
-
2. The user runs `/add-feature` inside the platform OSS repo (separate cwd/session). It reads
|
|
54
|
-
the work-order, implements, and opens its own MR there.
|
|
55
|
-
3. The consumer MR stays draft/blocked until the OSS MR merges and the consumer bumps the consumed
|
|
56
|
-
`@openora/*` version. Link the two by the work-order path and the ticket key.
|
|
57
|
-
4. Note the handoff in the Jira ticket and the consumer MR description so the dependency is
|
|
58
|
-
visible.
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: create-pr
|
|
3
|
-
|
|
4
|
-
description: Commit, push, and open a GitLab Merge Request following this repo's promotion chain (dev -> stage -> prod). Use on "create pr", "create mr", "open a pr/mr", "/create-pr", or "promote <branch>".
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
# create-pr
|
|
8
|
-
|
|
9
|
-
This repo is on **GitLab** (`<your-gitlab-project>`). "PR" = Merge Request. Use the `glab` CLI
|
|
10
|
-
(already installed). Environment branches are promoted along a fixed chain - never open an MR
|
|
11
|
-
straight to `prod` from a feature/dev branch.
|
|
12
|
-
|
|
13
|
-
## Promotion chain
|
|
14
|
-
|
|
15
|
-
| Source (current) branch | MR target | Notes |
|
|
16
|
-
| ---------------------------------- | --------- | ------------------------------------------- |
|
|
17
|
-
| `dev` (default working) | `stage` | Promote accumulated work to the staging env |
|
|
18
|
-
| `stage` | `prod` | Promote staging -> production |
|
|
19
|
-
| any `feat/*` / `fix/*` / `<KEY>-*` | `dev` | Feature/ticket work merges into dev first |
|
|
20
|
-
|
|
21
|
-
If the current branch isn't in the table, target `dev`.
|
|
22
|
-
|
|
23
|
-
## Steps
|
|
24
|
-
|
|
25
|
-
1. **Determine source + target.** `git branch --show-current` -> look it up in the
|
|
26
|
-
table above to get the target.
|
|
27
|
-
2. **Scope the commit.** `git status -s`. Commit ONLY changes that belong to this
|
|
28
|
-
unit of work. If unrelated/pre-existing edits are present, do NOT bundle them -
|
|
29
|
-
stage your files explicitly and tell the user what you left out. Never
|
|
30
|
-
`git add -A` blindly when foreign changes are in the tree.
|
|
31
|
-
3. **Commit.** Conventional-commit message (`feat:`, `fix:`, `docs:`, `refactor:`,
|
|
32
|
-
`chore:`); for ticket work prefix the ticket key (e.g. `feat(<KEY>-123): ...`).
|
|
33
|
-
4. **Verify before pushing** (cheap insurance): run the repo's check (e.g.
|
|
34
|
-
`pnpm check:types && pnpm check:lint`). Don't push a red tree.
|
|
35
|
-
5. **Push** the current branch - but STOP and get an explicit per-action "yes push"
|
|
36
|
-
from the user FIRST. Report the commit SHA, then ask. Invoking this skill is NOT
|
|
37
|
-
push authorization. Pushing to a shared/env branch (`dev`, `stage`, `prod`)
|
|
38
|
-
without that explicit yes is forbidden. Only after the yes: `git push -u origin <current>`.
|
|
39
|
-
6. **Open the MR** with glab (only after the push is confirmed + done):
|
|
40
|
-
```
|
|
41
|
-
glab mr create --source-branch <current> --target-branch <target> \
|
|
42
|
-
--title "<type>: <summary>" --description "<body>" --yes --remove-source-branch=false
|
|
43
|
-
```
|
|
44
|
-
For a long-lived env branch (`dev`, `stage`, `prod`) NEVER pass
|
|
45
|
-
`--remove-source-branch`. Reuse an existing open MR for the same source->target
|
|
46
|
-
instead of creating a duplicate
|
|
47
|
-
(`glab mr list --source-branch <current> --target-branch <target>`).
|
|
48
|
-
7. **Report** the MR URL back to the user.
|
|
49
|
-
|
|
50
|
-
## Rules
|
|
51
|
-
|
|
52
|
-
- NEVER push without an explicit per-action "yes push" from the user. Invoking this
|
|
53
|
-
skill does NOT authorize a push. Report the commit SHA, ask, then push only on yes.
|
|
54
|
-
- The repo check must pass before the push.
|
|
55
|
-
- Keep the MR scoped to one concern; split unrelated changes into separate MRs.
|