@synoikia/create-plugin 0.6.1 → 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/README.md +2 -13
- package/dist/cli.js +0 -4
- package/dist/cli.js.map +1 -1
- package/dist/scaffold.js +1 -6
- package/dist/scaffold.js.map +1 -1
- package/package.json +3 -3
- package/templates/plugin/common/README.md +9 -8
- package/templates/repo/base/README.md +34 -8
- package/templates/repo/base/_github/workflows/ci.yml +13 -2
- package/templates/repo/base/_gitignore +1 -0
- package/templates/repo/base/_gitleaks.toml +3 -0
- package/templates/repo/base/_prettierignore +2 -0
- package/templates/repo/base/package.json +2 -13
- package/templates/repo/claude/CLAUDE.md +17 -30
- package/templates/repo/claude/_claude/guard-paths.json +17 -0
- package/templates/repo/claude/_claude/hooks/check-plugins.mjs +39 -0
- package/templates/repo/claude/_claude/hooks/session-start.sh +8 -9
- package/templates/repo/claude/_claude/settings.json +17 -17
- package/dist/workflow/branch.d.ts +0 -2
- package/dist/workflow/branch.js +0 -31
- package/dist/workflow/branch.js.map +0 -1
- package/dist/workflow/config.d.ts +0 -24
- package/dist/workflow/config.js +0 -25
- package/dist/workflow/config.js.map +0 -1
- package/dist/workflow/features.d.ts +0 -20
- package/dist/workflow/features.js +0 -165
- package/dist/workflow/features.js.map +0 -1
- package/dist/workflow/git.d.ts +0 -12
- package/dist/workflow/git.js +0 -42
- package/dist/workflow/git.js.map +0 -1
- package/dist/workflow/glob.d.ts +0 -3
- package/dist/workflow/glob.js +0 -21
- package/dist/workflow/glob.js.map +0 -1
- package/dist/workflow/index.d.ts +0 -2
- package/dist/workflow/index.js +0 -162
- package/dist/workflow/index.js.map +0 -1
- package/dist/workflow/phases.d.ts +0 -22
- package/dist/workflow/phases.js +0 -50
- package/dist/workflow/phases.js.map +0 -1
- package/dist/workflow/prose.d.ts +0 -2
- package/dist/workflow/prose.js +0 -113
- package/dist/workflow/prose.js.map +0 -1
- package/dist/workflow/red.d.ts +0 -2
- package/dist/workflow/red.js +0 -36
- package/dist/workflow/red.js.map +0 -1
- package/dist/workflow/secrets.d.ts +0 -2
- package/dist/workflow/secrets.js +0 -5
- package/dist/workflow/secrets.js.map +0 -1
- package/dist/workflow/security-line.d.ts +0 -6
- package/dist/workflow/security-line.js +0 -14
- package/dist/workflow/security-line.js.map +0 -1
- package/dist/workflow/sync.d.ts +0 -4
- package/dist/workflow/sync.js +0 -47
- package/dist/workflow/sync.js.map +0 -1
- package/dist/workflow/tests.d.ts +0 -2
- package/dist/workflow/tests.js +0 -6
- package/dist/workflow/tests.js.map +0 -1
- package/templates/repo/claude/_claude/agents/security-reviewer.md +0 -33
- package/templates/repo/claude/_claude/hooks/format.mjs +0 -20
- package/templates/repo/claude/_claude/hooks/guard-paths.mjs +0 -43
- package/templates/repo/claude/_claude/skills/add-plugin/SKILL.md +0 -39
- package/templates/repo/claude/_claude/skills/release-plugin/SKILL.md +0 -18
- package/templates/workflow/base/_githooks/pre-commit +0 -3
- package/templates/workflow/base/_githooks/pre-push +0 -4
- package/templates/workflow/base/_github/workflows/workflow.yml +0 -45
- package/templates/workflow/base/docs/workflow.md +0 -100
- package/templates/workflow/claude/_claude/hooks/guard-branch.mjs +0 -73
- package/templates/workflow/claude/_claude/skills/architect/SKILL.md +0 -44
- package/templates/workflow/claude/_claude/skills/change-workflow/SKILL.md +0 -70
- package/templates/workflow/claude/_claude/skills/developer/SKILL.md +0 -57
- package/templates/workflow/claude/_claude/skills/requirements-engineer/SKILL.md +0 -59
- package/templates/workflow/claude/_claude/skills/technical-writing/SKILL.md +0 -35
package/dist/workflow/prose.js
DELETED
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
const MAX_WORDS = 25;
|
|
2
|
-
/** Inline code and link targets are not prose. */
|
|
3
|
-
const stripInline = (text) => text
|
|
4
|
-
.replace(/`[^`]*`/g, 'Code')
|
|
5
|
-
.replace(/\]\([^)]*\)/g, ']')
|
|
6
|
-
.replace(/<!--.*?-->/g, '')
|
|
7
|
-
.replace(/https?:\/\/\S+/g, 'link');
|
|
8
|
-
function markdownBlocks(text) {
|
|
9
|
-
const lines = text.split('\n');
|
|
10
|
-
const blocks = [];
|
|
11
|
-
let fence;
|
|
12
|
-
let comment = false;
|
|
13
|
-
let para;
|
|
14
|
-
const flush = () => {
|
|
15
|
-
if (para)
|
|
16
|
-
blocks.push(para);
|
|
17
|
-
para = undefined;
|
|
18
|
-
};
|
|
19
|
-
let i = 0;
|
|
20
|
-
if (lines[0] === '---') {
|
|
21
|
-
const end = lines.indexOf('---', 1);
|
|
22
|
-
if (end > 0)
|
|
23
|
-
i = end + 1;
|
|
24
|
-
}
|
|
25
|
-
for (; i < lines.length; i++) {
|
|
26
|
-
const raw = lines[i];
|
|
27
|
-
const line = i + 1;
|
|
28
|
-
const fenceMark = raw.match(/^\s*(`{3,}|~{3,})/)?.[1];
|
|
29
|
-
if (fence) {
|
|
30
|
-
if (fenceMark?.startsWith(fence))
|
|
31
|
-
fence = undefined;
|
|
32
|
-
continue;
|
|
33
|
-
}
|
|
34
|
-
if (fenceMark) {
|
|
35
|
-
flush();
|
|
36
|
-
fence = fenceMark;
|
|
37
|
-
continue;
|
|
38
|
-
}
|
|
39
|
-
if (comment || raw.trim().startsWith('<!--')) {
|
|
40
|
-
comment = !raw.includes('-->');
|
|
41
|
-
continue;
|
|
42
|
-
}
|
|
43
|
-
if (!raw.trim() || (/^\s{4,}\S/.test(raw) && !para)) {
|
|
44
|
-
flush();
|
|
45
|
-
continue;
|
|
46
|
-
}
|
|
47
|
-
if (/^\s*\|/.test(raw)) {
|
|
48
|
-
flush();
|
|
49
|
-
if (!/^\s*\|[\s:|-]+\|\s*$/.test(raw))
|
|
50
|
-
for (const cell of raw.split('|').slice(1, -1))
|
|
51
|
-
blocks.push({ line, lastLine: line, text: cell, label: true });
|
|
52
|
-
continue;
|
|
53
|
-
}
|
|
54
|
-
if (/^\s*#{1,6}\s/.test(raw)) {
|
|
55
|
-
flush();
|
|
56
|
-
blocks.push({ line, lastLine: line, text: raw.replace(/^\s*#+\s*/, ''), label: true });
|
|
57
|
-
continue;
|
|
58
|
-
}
|
|
59
|
-
const item = raw.match(/^\s*(?:[-*+]|\d+[.)])\s+(.*)$/);
|
|
60
|
-
if (item) {
|
|
61
|
-
flush();
|
|
62
|
-
para = { line, lastLine: line, text: item[1], label: false };
|
|
63
|
-
continue;
|
|
64
|
-
}
|
|
65
|
-
if (para) {
|
|
66
|
-
para.text += ` ${raw.trim()}`;
|
|
67
|
-
para.lastLine = line;
|
|
68
|
-
}
|
|
69
|
-
else
|
|
70
|
-
para = { line, lastLine: line, text: raw.trim().replace(/^>\s*/, ''), label: false };
|
|
71
|
-
}
|
|
72
|
-
flush();
|
|
73
|
-
return blocks;
|
|
74
|
-
}
|
|
75
|
-
function featureBlocks(text) {
|
|
76
|
-
return text.split('\n').flatMap((raw, i) => {
|
|
77
|
-
const t = raw.trim();
|
|
78
|
-
if (!t || t.startsWith('#') || t.startsWith('@'))
|
|
79
|
-
return [];
|
|
80
|
-
const label = t.startsWith('|') || /^[A-Za-z ]+:/.test(t);
|
|
81
|
-
return [{ line: i + 1, lastLine: i + 1, text: t.replace(/^[A-Za-z ]+:\s*/, ''), label }];
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
const words = (sentence) => sentence.split(/\s+/).filter((w) => /[\p{L}\p{N}]/u.test(w)).length;
|
|
85
|
-
/** Problems with the writing rules in one file. With `lines`, only blocks that touch those lines. */
|
|
86
|
-
export function checkProse(file, text, lines) {
|
|
87
|
-
const blocks = file.endsWith('.feature') ? featureBlocks(text) : markdownBlocks(text);
|
|
88
|
-
const issues = [];
|
|
89
|
-
for (const block of blocks) {
|
|
90
|
-
if (lines) {
|
|
91
|
-
let touched = false;
|
|
92
|
-
for (let l = block.line; l <= block.lastLine && !touched; l++)
|
|
93
|
-
touched = lines.has(l);
|
|
94
|
-
if (!touched)
|
|
95
|
-
continue;
|
|
96
|
-
}
|
|
97
|
-
const prose = stripInline(block.text);
|
|
98
|
-
const at = `${file}:${block.line}`;
|
|
99
|
-
if (prose.includes(';'))
|
|
100
|
-
issues.push(`${at}: no semicolons. Write two sentences`);
|
|
101
|
-
if (prose.includes('—'))
|
|
102
|
-
issues.push(`${at}: no em dashes. Write two sentences or use a colon`);
|
|
103
|
-
if (block.label)
|
|
104
|
-
continue;
|
|
105
|
-
for (const sentence of prose.split(/(?<=[.!?:][*_)"'`]*)\s+(?=[\p{Lu}`*"([])/u)) {
|
|
106
|
-
const n = words(sentence);
|
|
107
|
-
if (n > MAX_WORDS)
|
|
108
|
-
issues.push(`${at}: a sentence has ${n} words. Use ${MAX_WORDS} at most`);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
return issues;
|
|
112
|
-
}
|
|
113
|
-
//# sourceMappingURL=prose.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"prose.js","sourceRoot":"","sources":["../../src/workflow/prose.ts"],"names":[],"mappings":"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"}
|
package/dist/workflow/red.d.ts
DELETED
package/dist/workflow/red.js
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { spawnSync } from 'node:child_process';
|
|
2
|
-
import { existsSync, mkdtempSync, rmSync } from 'node:fs';
|
|
3
|
-
import { tmpdir } from 'node:os';
|
|
4
|
-
import path from 'node:path';
|
|
5
|
-
import { git } from './git.js';
|
|
6
|
-
const run = (cwd, cmd, args) => spawnSync(cmd, args, { cwd, stdio: 'inherit', env: { ...process.env, CI: 'true' } }).status ?? 1;
|
|
7
|
-
/** The nearest directory up from `file` with a package.json, below `root`. */
|
|
8
|
-
function packageOf(root, file) {
|
|
9
|
-
let dir = path.dirname(path.join(root, file));
|
|
10
|
-
while (dir !== root && !existsSync(path.join(dir, 'package.json')))
|
|
11
|
-
dir = path.dirname(dir);
|
|
12
|
-
return dir;
|
|
13
|
-
}
|
|
14
|
-
/** Runs the feature files at `sha` in a temporary worktree. True when at least one package fails. */
|
|
15
|
-
export function featuresFailAt(root, sha, features) {
|
|
16
|
-
const dir = mkdtempSync(path.join(tmpdir(), 'synoikia-red-'));
|
|
17
|
-
try {
|
|
18
|
-
git(root, 'worktree', 'add', '--detach', dir, sha);
|
|
19
|
-
if (run(dir, 'pnpm', ['install', '--frozen-lockfile', '--prefer-offline']) !== 0)
|
|
20
|
-
throw new Error(`pnpm install failed at ${sha.slice(0, 7)}`);
|
|
21
|
-
const byPackage = new Map();
|
|
22
|
-
for (const f of features.filter((f) => existsSync(path.join(dir, f)))) {
|
|
23
|
-
const pkg = packageOf(dir, f);
|
|
24
|
-
byPackage.set(pkg, [...(byPackage.get(pkg) ?? []), path.relative(pkg, path.join(dir, f))]);
|
|
25
|
-
}
|
|
26
|
-
let failed = false;
|
|
27
|
-
for (const [pkg, files] of byPackage)
|
|
28
|
-
failed = run(pkg, 'pnpm', ['exec', 'vitest', 'run', ...files]) !== 0 || failed;
|
|
29
|
-
return failed;
|
|
30
|
-
}
|
|
31
|
-
finally {
|
|
32
|
-
spawnSync('git', ['worktree', 'remove', '--force', dir], { cwd: root });
|
|
33
|
-
rmSync(dir, { recursive: true, force: true });
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
//# sourceMappingURL=red.js.map
|
package/dist/workflow/red.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"red.js","sourceRoot":"","sources":["../../src/workflow/red.ts"],"names":[],"mappings":"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"}
|
package/dist/workflow/secrets.js
DELETED
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
const EXAMPLE = /(?:\.|^|\/)(?:example|sample|template)(?:\.[^/]+)?$/;
|
|
2
|
-
const CREDENTIAL = /(?:^|\/)(?:secrets?(?:\.[^/]+)*\.ya?ml|\.env(?:\..+)?|\.dev\.vars(?:\..+)?|[^/]+\.secret(?:\..+)?|[^/]+\.(?:pem|key|p12|pfx)|id_(?:rsa|dsa|ecdsa|ed25519)|\.npmrc|\.netrc)$/;
|
|
3
|
-
/** Staged paths that look like credential files, by name rather than content. */
|
|
4
|
-
export const credentialPaths = (files) => files.filter((f) => !EXAMPLE.test(f) && CREDENTIAL.test(f));
|
|
5
|
-
//# sourceMappingURL=secrets.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"secrets.js","sourceRoot":"","sources":["../../src/workflow/secrets.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,GAAG,qDAAqD,CAAC;AACtE,MAAM,UAAU,GACd,6KAA6K,CAAC;AAEhL,iFAAiF;AACjF,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,KAAwB,EAAE,EAAE,CAC1D,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC"}
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
export declare const SECURITY_LINE: RegExp;
|
|
2
|
-
/** A ready PR that changes a security path states its audit on a line of its own. */
|
|
3
|
-
export declare function checkSecurityLine(pr: {
|
|
4
|
-
draft: boolean;
|
|
5
|
-
body: string | null;
|
|
6
|
-
}, changed: readonly string[], paths: readonly string[]): string[];
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { matchesAny } from './glob.js';
|
|
2
|
-
export const SECURITY_LINE = /^Security audit: \S/m;
|
|
3
|
-
/** A ready PR that changes a security path states its audit on a line of its own. */
|
|
4
|
-
export function checkSecurityLine(pr, changed, paths) {
|
|
5
|
-
if (pr.draft)
|
|
6
|
-
return [];
|
|
7
|
-
const touched = changed.filter((f) => matchesAny(f, paths));
|
|
8
|
-
if (!touched.length || SECURITY_LINE.test(pr.body ?? ''))
|
|
9
|
-
return [];
|
|
10
|
-
return [
|
|
11
|
-
`this PR changes security paths (${touched.slice(0, 5).join(', ')}${touched.length > 5 ? ', …' : ''}). Add a line "Security audit: <boundary and findings>" or "Security audit: none. <why>" to its description`,
|
|
12
|
-
];
|
|
13
|
-
}
|
|
14
|
-
//# sourceMappingURL=security-line.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"security-line.js","sourceRoot":"","sources":["../../src/workflow/security-line.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAEvC,MAAM,CAAC,MAAM,aAAa,GAAG,sBAAsB,CAAC;AAEpD,qFAAqF;AACrF,MAAM,UAAU,iBAAiB,CAC/B,EAA2C,EAC3C,OAA0B,EAC1B,KAAwB;IAExB,IAAI,EAAE,CAAC,KAAK;QAAE,OAAO,EAAE,CAAC;IACxB,MAAM,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC;IAC5D,IAAI,CAAC,OAAO,CAAC,MAAM,IAAI,aAAa,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,IAAI,EAAE,CAAC;QAAE,OAAO,EAAE,CAAC;IACpE,OAAO;QACL,mCAAmC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,6GAA6G;KACjN,CAAC;AACJ,CAAC"}
|
package/dist/workflow/sync.d.ts
DELETED
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
/** The shared workflow files, as repo-relative path → template path. Claude files only for repos that use Claude. */
|
|
2
|
-
export declare function workflowFiles(root: string): Map<string, string>;
|
|
3
|
-
/** Writes the shared files, or with `check` lists the ones that differ. Returns the paths written or out of date. */
|
|
4
|
-
export declare function syncWorkflow(root: string, check?: boolean): string[];
|
package/dist/workflow/sync.js
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import { chmodSync, existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, realpathSync, statSync, writeFileSync, } from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import { TEMPLATES_DIR } from '../repo-root.js';
|
|
4
|
-
const WORKFLOW_DIR = path.join(TEMPLATES_DIR, 'workflow');
|
|
5
|
-
const outName = (name) => (/^_[a-zA-Z]/.test(name) ? `.${name.slice(1)}` : name);
|
|
6
|
-
/** The shared workflow files, as repo-relative path → template path. Claude files only for repos that use Claude. */
|
|
7
|
-
export function workflowFiles(root) {
|
|
8
|
-
const files = new Map();
|
|
9
|
-
const walk = (from, to) => {
|
|
10
|
-
for (const entry of readdirSync(from).sort()) {
|
|
11
|
-
const src = path.join(from, entry);
|
|
12
|
-
const dest = path.posix.join(to, outName(entry));
|
|
13
|
-
if (statSync(src).isDirectory())
|
|
14
|
-
walk(src, dest);
|
|
15
|
-
else
|
|
16
|
-
files.set(dest, src);
|
|
17
|
-
}
|
|
18
|
-
};
|
|
19
|
-
walk(path.join(WORKFLOW_DIR, 'base'), '');
|
|
20
|
-
if (existsSync(path.join(root, '.claude')))
|
|
21
|
-
walk(path.join(WORKFLOW_DIR, 'claude'), '');
|
|
22
|
-
return files;
|
|
23
|
-
}
|
|
24
|
-
/** Writes the shared files, or with `check` lists the ones that differ. Returns the paths written or out of date. */
|
|
25
|
-
export function syncWorkflow(root, check = false) {
|
|
26
|
-
const changed = [];
|
|
27
|
-
for (const [rel, src] of workflowFiles(root)) {
|
|
28
|
-
const dest = path.join(root, rel);
|
|
29
|
-
if (lstatSync(dest, { throwIfNoEntry: false })?.isSymbolicLink())
|
|
30
|
-
throw new Error(`${rel} is a symlink. Remove it before the sync`);
|
|
31
|
-
const text = readFileSync(src, 'utf8');
|
|
32
|
-
if (existsSync(dest) && readFileSync(dest, 'utf8') === text)
|
|
33
|
-
continue;
|
|
34
|
-
changed.push(rel);
|
|
35
|
-
if (check)
|
|
36
|
-
continue;
|
|
37
|
-
mkdirSync(path.dirname(dest), { recursive: true });
|
|
38
|
-
const real = realpathSync(path.dirname(dest));
|
|
39
|
-
if (path.relative(realpathSync(root), real).startsWith('..'))
|
|
40
|
-
throw new Error(`${rel} resolves outside the repository`);
|
|
41
|
-
writeFileSync(dest, text);
|
|
42
|
-
if (rel.startsWith('.githooks/'))
|
|
43
|
-
chmodSync(dest, 0o755);
|
|
44
|
-
}
|
|
45
|
-
return changed;
|
|
46
|
-
}
|
|
47
|
-
//# sourceMappingURL=sync.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"sync.js","sourceRoot":"","sources":["../../src/workflow/sync.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,SAAS,EACT,UAAU,EACV,SAAS,EACT,SAAS,EACT,WAAW,EACX,YAAY,EACZ,YAAY,EACZ,QAAQ,EACR,aAAa,GACd,MAAM,SAAS,CAAC;AACjB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAEhD,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,UAAU,CAAC,CAAC;AAC1D,MAAM,OAAO,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;AAEzF,qHAAqH;AACrH,MAAM,UAAU,aAAa,CAAC,IAAY;IACxC,MAAM,KAAK,GAAG,IAAI,GAAG,EAAkB,CAAC;IACxC,MAAM,IAAI,GAAG,CAAC,IAAY,EAAE,EAAU,EAAE,EAAE;QACxC,KAAK,MAAM,KAAK,IAAI,WAAW,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;YAC7C,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;YACnC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;YACjD,IAAI,QAAQ,CAAC,GAAG,CAAC,CAAC,WAAW,EAAE;gBAAE,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;;gBAC5C,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;QAC5B,CAAC;IACH,CAAC,CAAC;IACF,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC;IAC1C,IAAI,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;QAAE,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,QAAQ,CAAC,EAAE,EAAE,CAAC,CAAC;IACxF,OAAO,KAAK,CAAC;AACf,CAAC;AAED,qHAAqH;AACrH,MAAM,UAAU,YAAY,CAAC,IAAY,EAAE,KAAK,GAAG,KAAK;IACtD,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,KAAK,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC,IAAI,aAAa,CAAC,IAAI,CAAC,EAAE,CAAC;QAC7C,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;QAClC,IAAI,SAAS,CAAC,IAAI,EAAE,EAAE,cAAc,EAAE,KAAK,EAAE,CAAC,EAAE,cAAc,EAAE;YAC9D,MAAM,IAAI,KAAK,CAAC,GAAG,GAAG,0CAA0C,CAAC,CAAC;QACpE,MAAM,IAAI,GAAG,YAAY,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;QACvC,IAAI,UAAU,CAAC,IAAI,CAAC,IAAI,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,KAAK,IAAI;YAAE,SAAS;QACtE,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAClB,IAAI,KAAK;YAAE,SAAS;QACpB,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACnD,MAAM,IAAI,GAAG,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;QAC9C,IAAI,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC;YAC1D,MAAM,IAAI,KAAK,CAAC,GAAG,GAAG,kCAAkC,CAAC,CAAC;QAC5D,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QAC1B,IAAI,GAAG,CAAC,UAAU,CAAC,YAAY,CAAC;YAAE,SAAS,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IAC3D,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC"}
|
package/dist/workflow/tests.d.ts
DELETED
package/dist/workflow/tests.js
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import { matchesAny } from './glob.js';
|
|
2
|
-
/** New test files must be pure-function unit tests. Behavior is specified in feature files. */
|
|
3
|
-
export const unlistedTests = (added, unitTests) => added
|
|
4
|
-
.filter((f) => /\.(?:test|spec)\.[cm]?[jt]sx?$/.test(f) && !matchesAny(f, unitTests))
|
|
5
|
-
.map((f) => `${f}: a new test file must be a pure-function unit test (${unitTests.join(', ')}). Specify behavior in a feature file`);
|
|
6
|
-
//# sourceMappingURL=tests.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"tests.js","sourceRoot":"","sources":["../../src/workflow/tests.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAEvC,+FAA+F;AAC/F,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,KAAwB,EAAE,SAA4B,EAAE,EAAE,CACtF,KAAK;KACF,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,gCAAgC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC;KACpF,GAAG,CACF,CAAC,CAAC,EAAE,EAAE,CACJ,GAAG,CAAC,wDAAwD,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,uCAAuC,CAC1H,CAAC"}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: security-reviewer
|
|
3
|
-
description: Reviews a change to a Synoikia plugin, or to this repository's release workflow, for security problems and known CVEs in its dependencies. Use proactively before opening or merging a PR that touches plugins/*/manifest.json, plugins/*/plugin.yaml, plugins/*/src, a plugin's dependencies or .github/workflows; or when asked for a security review or dependency audit.
|
|
4
|
-
tools: Read, Grep, Glob, Bash
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
You review changes to Synoikia plugins for security problems. You are read-only: never edit files, commit, push or change the environment. Use Bash only for read-only commands (`git diff`, `git log`, `pnpm audit`, `pnpm why`, `pnpm ls`) and for running existing tests.
|
|
8
|
-
|
|
9
|
-
## The trust model
|
|
10
|
-
|
|
11
|
-
Synoikia core runs every plugin as a permission-confined child and puts every call through its own sandbox, permission gate, human approvals, redaction and audit log. Core enforces policy based on what the plugin tells it. A plugin that labels a destructive call `read`, leaves a secret out of `sensitiveKeys`, or writes a misleading approval summary weakens every install, and core can't detect it. Most findings are cases where the plugin gave core wrong information.
|
|
12
|
-
|
|
13
|
-
## Scope
|
|
14
|
-
|
|
15
|
-
Start from the diff you were given, otherwise `git diff origin/main...HEAD` plus uncommitted changes. Read the plugin's `manifest.json` and `plugin.yaml` in full whenever its `src/` changes.
|
|
16
|
-
|
|
17
|
-
## What to check
|
|
18
|
-
|
|
19
|
-
- **Classification** (`plugin.yaml` rules, discovery in `src/`): no operation with side effects is labeled read, including GETs that act. Destructive or hard-to-undo operations are `locked`. Unknown operations fail closed. A `classification: read` override needs a reason; removing a `locked`, `sensitiveParams` or `sensitiveResult` rule, or widening `include` past an `exclude`, is a finding unless the PR justifies it. `splitWhen` predicates must fail closed (unknown means the locked twin).
|
|
20
|
-
- **Request building**: model-supplied arguments cannot change the host, scheme or port, path parameters are encoded, model arguments cannot set auth headers, and the operation key core gated is the one invoked.
|
|
21
|
-
- **Summaries and confirm literals**: the approval shows exactly what will be affected; a literal names the thing at risk, and crafted params can't make it misleading. Summaries never contain secrets.
|
|
22
|
-
- **Secrets**: every credential field is `writeOnly`, uses the `secret` widget and is in `sensitiveKeys`; every secret field the upstream returns is in `sensitiveKeys` or a `sensitiveResult` rule, with an e2e check. Credentials never appear in errors, logs or catalog text, and are never sent to any host but the upstream.
|
|
23
|
-
- **Network**: `network.hosts` is minimal; TLS verification is on by default.
|
|
24
|
-
- **Staying inside core's model**: no plugin-side allow/deny lists, approval prompts or caching that serves data after core denied a call; no `eval`, `new Function` or dynamic imports; nothing loaded from disk at runtime (`plugin.yaml` is inlined at build time).
|
|
25
|
-
- **Untrusted upstream data**: specs, JSON and frames are untrusted; look for prototype pollution, unbounded sizes and YAML beyond plain data.
|
|
26
|
-
|
|
27
|
-
## Known vulnerabilities
|
|
28
|
-
|
|
29
|
-
Run `pnpm audit --json` and `pnpm audit --prod`. For each advisory, `pnpm why <package>`: a plugin's `dependencies` are bundled and ship to every install, so judge reachability; `devDependencies` run only in CI. Give the advisory ID, affected and patched ranges, the dependency path and the smallest fix. If the audit can't reach the registry, say so.
|
|
30
|
-
|
|
31
|
-
## Reporting
|
|
32
|
-
|
|
33
|
-
Report only issues tied to concrete code or a concrete advisory: severity (critical, high, medium, low), location, what breaks, a scenario, and the smallest fix with the test that would catch a regression. If there are none, say so and list what you checked.
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
// PostToolUse hook: runs Prettier on the file just edited, so `pnpm format:check` in CI stays green.
|
|
2
|
-
// Prettier skips files in .prettierignore and unknown types; a failure here never blocks the edit.
|
|
3
|
-
import { execFileSync } from 'node:child_process';
|
|
4
|
-
import { existsSync } from 'node:fs';
|
|
5
|
-
import path from 'node:path';
|
|
6
|
-
|
|
7
|
-
let input = '';
|
|
8
|
-
for await (const chunk of process.stdin) input += chunk;
|
|
9
|
-
const { tool_input: toolInput = {}, cwd } = JSON.parse(input || '{}');
|
|
10
|
-
const root = process.env.CLAUDE_PROJECT_DIR ?? cwd ?? process.cwd();
|
|
11
|
-
const prettier = path.join(root, 'node_modules/.bin/prettier');
|
|
12
|
-
const file = toolInput.file_path;
|
|
13
|
-
|
|
14
|
-
if (file && existsSync(file) && existsSync(prettier)) {
|
|
15
|
-
try {
|
|
16
|
-
execFileSync(prettier, ['--write', '--ignore-unknown', '--log-level=warn', file], { cwd: root, stdio: 'ignore' });
|
|
17
|
-
} catch {
|
|
18
|
-
// A file Prettier can't parse is left as written; lint and format:check will report it.
|
|
19
|
-
}
|
|
20
|
-
}
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
// PreToolUse hook: stops edits to generated files and asks before edits to the signing key.
|
|
2
|
-
// Reads the tool call from stdin; prints a permission decision for matching paths, nothing otherwise.
|
|
3
|
-
import path from 'node:path';
|
|
4
|
-
|
|
5
|
-
const RULES = [
|
|
6
|
-
{
|
|
7
|
-
match: /(^|\/)pnpm-lock\.yaml$/,
|
|
8
|
-
decision: 'deny',
|
|
9
|
-
reason: 'pnpm-lock.yaml is generated. Change package.json and run `pnpm install` instead.',
|
|
10
|
-
},
|
|
11
|
-
{
|
|
12
|
-
match: /^plugins\/[^/]+\/dist\//,
|
|
13
|
-
decision: 'deny',
|
|
14
|
-
reason: 'dist/ is the esbuild bundle. Edit src/ and run `pnpm --filter ./plugins/<id> build`.',
|
|
15
|
-
},
|
|
16
|
-
{
|
|
17
|
-
match: /^minisign\.pub$/,
|
|
18
|
-
decision: 'ask',
|
|
19
|
-
reason:
|
|
20
|
-
'minisign.pub is the key every Synoikia install pins for this repository. Changing it blocks installs until each admin confirms the new key; only for a compromised key.',
|
|
21
|
-
},
|
|
22
|
-
];
|
|
23
|
-
|
|
24
|
-
let input = '';
|
|
25
|
-
for await (const chunk of process.stdin) input += chunk;
|
|
26
|
-
const { tool_input: toolInput = {}, cwd } = JSON.parse(input || '{}');
|
|
27
|
-
const file = toolInput.file_path ?? toolInput.notebook_path;
|
|
28
|
-
if (!file) process.exit(0);
|
|
29
|
-
|
|
30
|
-
const root = process.env.CLAUDE_PROJECT_DIR ?? cwd ?? process.cwd();
|
|
31
|
-
const rel = path.relative(root, path.resolve(root, file)).split(path.sep).join('/');
|
|
32
|
-
const rule = RULES.find((r) => r.match.test(rel));
|
|
33
|
-
if (!rule) process.exit(0);
|
|
34
|
-
|
|
35
|
-
console.log(
|
|
36
|
-
JSON.stringify({
|
|
37
|
-
hookSpecificOutput: {
|
|
38
|
-
hookEventName: 'PreToolUse',
|
|
39
|
-
permissionDecision: rule.decision,
|
|
40
|
-
permissionDecisionReason: `${rel}: ${rule.reason}`,
|
|
41
|
-
},
|
|
42
|
-
}),
|
|
43
|
-
);
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: add-plugin
|
|
3
|
-
description: Scaffold a new Synoikia plugin under plugins/<id> with `pnpm new`, then fill in its plugin.yaml rules, discovery and tests. Use when asked to add, create or start a plugin for a new upstream service.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Add a plugin
|
|
7
|
-
|
|
8
|
-
## 1. Settle the basics
|
|
9
|
-
|
|
10
|
-
- **`<id>`**: lowercase, the directory name and manifest `id`; the sandbox namespace defaults to it in camelCase.
|
|
11
|
-
- **Archetype**: `openapi-rest` (the upstream serves an OpenAPI spec), `static-rest` (a REST API without one; operations are declared in `plugin.yaml`), `websocket-rpc` (JSON-RPC over WebSocket), or `blank`.
|
|
12
|
-
- **Auth**: `bearer`, `api-key` (with its header), `basic` or `none`.
|
|
13
|
-
- **Destructive operations**: which ones are `locked` (always a human who types a confirmation literal), and what that literal is.
|
|
14
|
-
- **Secrets**: which connection fields are credentials, and which fields the upstream returns hold secrets.
|
|
15
|
-
|
|
16
|
-
## 2. Scaffold
|
|
17
|
-
|
|
18
|
-
```sh
|
|
19
|
-
pnpm new --id <id> --name "<Name>" --archetype <archetype> --auth <auth> --yes
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
This writes `plugins/<id>/` (manifest, `plugin.yaml`, `src/`, a fake upstream, unit and e2e tests) and runs `pnpm install`. The generated plugin already builds and passes its tests.
|
|
23
|
-
|
|
24
|
-
## 3. Describe the upstream
|
|
25
|
-
|
|
26
|
-
- `manifest.json`: connection fields and help, `sensitiveKeys` (add every secret field the upstream returns), `network.hosts`.
|
|
27
|
-
- `plugin.yaml`: `rules` for locks, splits, classification overrides, `sensitiveParams`, `sensitiveResult`, `confirm` literals and summary notes; `exclude` for plumbing the model has no business calling. A rule's `classification` can never unlock a locked operation, and anything unclassified is a write.
|
|
28
|
-
- `src/plugin.ts`: only what needs logic, such as a `splitWhen` predicate or a custom confirm source.
|
|
29
|
-
|
|
30
|
-
## 4. Tests
|
|
31
|
-
|
|
32
|
-
Extend `test/fake-upstream.ts` to answer what the plugin calls, including responses with secrets. Fill in `test/compliance.yaml`: a case for each locked operation, and each secret the fake returns. Core's compliance suite must print that the plugin is fit for production. Specify plugin-specific behavior in `features/*.feature` with the suite's steps (`docs/plugin-authoring.md`).
|
|
33
|
-
|
|
34
|
-
```sh
|
|
35
|
-
pnpm --filter ./plugins/<id> test
|
|
36
|
-
pnpm typecheck && pnpm lint && pnpm format:check
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
Then run the `security-reviewer` subagent on the diff.
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: release-plugin
|
|
3
|
-
description: Prepare a plugin release by bumping its version in manifest.json and package.json together, and optionally its minimum Synoikia version. Use when asked to release, publish, ship or bump the version of a plugin.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Release a plugin
|
|
7
|
-
|
|
8
|
-
A release is a version bump merged to `main`; the Release workflow packs, signs, verifies and publishes every plugin whose version isn't in the published index yet (see `RELEASING.md`).
|
|
9
|
-
|
|
10
|
-
1. **Pick the version** with semver against the last release (`git log --oneline -- plugins/<id>`):
|
|
11
|
-
- patch: fixes, catalog corrections, wording;
|
|
12
|
-
- minor: new operations, connection fields or capabilities; also any `plugin.yaml` change that unlocks an operation, drops a `sensitiveParams`/`sensitiveResult` rule or overrides a classification to read (call it out in the PR);
|
|
13
|
-
- major: a change that breaks existing instances, such as a renamed operation key or a removed connection field.
|
|
14
|
-
2. **Bump both files to the same version**: `plugins/<id>/manifest.json` and `plugins/<id>/package.json`.
|
|
15
|
-
3. **Minimum Synoikia version**, only when the change needs a newer core: `"synoikia": { "minCoreVersion": "x.y.z" }` in `package.json`. A newer SDK means bumping the manifest `sdk` range together with the `@synoikia/plugin-sdk` range, then `pnpm install`.
|
|
16
|
-
4. **Verify**: `pnpm --filter ./plugins/<id> test`, then `pnpm typecheck && pnpm lint && pnpm format:check`.
|
|
17
|
-
|
|
18
|
-
Released versions are immutable. Never touch `minisign.pub` or the signing steps of the workflow.
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
# Written by `synoikia-plugin workflow sync`. Edit the template in via-justa/synoikia-core instead.
|
|
2
|
-
name: Workflow
|
|
3
|
-
|
|
4
|
-
on:
|
|
5
|
-
pull_request:
|
|
6
|
-
types: [opened, edited, synchronize, reopened, ready_for_review, labeled, unlabeled]
|
|
7
|
-
|
|
8
|
-
permissions:
|
|
9
|
-
contents: read
|
|
10
|
-
|
|
11
|
-
jobs:
|
|
12
|
-
workflow:
|
|
13
|
-
runs-on: ubuntu-latest
|
|
14
|
-
env:
|
|
15
|
-
BACKFILL: ${{ contains(github.event.pull_request.labels.*.name, 'backfill') && '--backfill' || '' }}
|
|
16
|
-
BASE: ${{ github.event.pull_request.base.sha }}
|
|
17
|
-
steps:
|
|
18
|
-
- uses: actions/checkout@v4
|
|
19
|
-
with:
|
|
20
|
-
ref: ${{ github.event.pull_request.head.sha }}
|
|
21
|
-
fetch-depth: 0
|
|
22
|
-
persist-credentials: false
|
|
23
|
-
- uses: pnpm/action-setup@v4
|
|
24
|
-
- uses: actions/setup-node@v4
|
|
25
|
-
with:
|
|
26
|
-
node-version-file: .nvmrc
|
|
27
|
-
cache: pnpm
|
|
28
|
-
- run: pnpm install --frozen-lockfile
|
|
29
|
-
- run: pnpm workflow check-branch
|
|
30
|
-
- run: pnpm workflow check-features --base "$BASE" $BACKFILL
|
|
31
|
-
- run: pnpm workflow check-prose --base "$BASE" --pr-body
|
|
32
|
-
- run: pnpm workflow check-security-line --base "$BASE"
|
|
33
|
-
- run: pnpm workflow check-phases --base "$BASE" --red-green $BACKFILL
|
|
34
|
-
|
|
35
|
-
gitleaks:
|
|
36
|
-
runs-on: ubuntu-latest
|
|
37
|
-
steps:
|
|
38
|
-
- uses: actions/checkout@v4
|
|
39
|
-
with:
|
|
40
|
-
fetch-depth: 0
|
|
41
|
-
persist-credentials: false
|
|
42
|
-
- uses: gitleaks/gitleaks-action@ff98106e4c7b2bc287b24eaf42907196329070c7 # v2.3.9
|
|
43
|
-
env:
|
|
44
|
-
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
45
|
-
GITLEAKS_ENABLE_COMMENTS: false
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
# Workflow
|
|
2
|
-
|
|
3
|
-
This repository builds every change spec first and test first. The rules below bind every contributor. CI checks them on each pull request. The git hooks run the same checks before a commit and a push.
|
|
4
|
-
|
|
5
|
-
`synoikia-plugin workflow sync` writes this file from `@synoikia/create-plugin`. Do not edit it here. Change the template in `via-justa/synoikia-core` instead.
|
|
6
|
-
|
|
7
|
-
## Steps
|
|
8
|
-
|
|
9
|
-
One change is one slice: one rule of one feature, with three scenarios at most. Each slice gets its own branch and pull request.
|
|
10
|
-
|
|
11
|
-
1. **Spec.** Write the scenarios in a feature file. Commit them as `spec: …`.
|
|
12
|
-
2. **Architecture and plan.** Write the build plan: the slices in order, and the existing code each one reuses. Put it in the pull request description. Place the slice in the model in `docs/architecture/` when it adds or moves a part. Commit as `arch: …`.
|
|
13
|
-
3. **Red.** Write the step definitions. Run the scenarios and see them fail because the behavior is missing. Commit as `test: …`.
|
|
14
|
-
4. **Green.** Write the least code that makes the scenarios pass. Use existing code that does the same work. Commit as `feat: …` or `fix: …`.
|
|
15
|
-
5. **Refactor.** Clean up with the scenarios green. Extract code that duplicates the same behavior into one shared function. Commit as `refactor: …`.
|
|
16
|
-
6. **Pull request.** Push and open the pull request. Fill in the template.
|
|
17
|
-
|
|
18
|
-
## Commits
|
|
19
|
-
|
|
20
|
-
Each commit belongs to one phase. Its subject starts with the phase, and it changes only the files of that phase.
|
|
21
|
-
|
|
22
|
-
| Prefix | Changes |
|
|
23
|
-
| ---------------------- | ------------------------------------------------------------------ |
|
|
24
|
-
| `spec:` | Feature files only |
|
|
25
|
-
| `arch:` | The architecture model only |
|
|
26
|
-
| `test:` | Step definitions, the World and unit tests only |
|
|
27
|
-
| `feat:` `fix:` | Code. Not feature files and not the model |
|
|
28
|
-
| `refactor:` | Code with the scenarios green. Not feature files and not the model |
|
|
29
|
-
| `docs:` `chore:` `ci:` | Documents, tooling and CI. Not feature files and not the model |
|
|
30
|
-
|
|
31
|
-
A `feat:` or `fix:` commit that changes product code needs a `test:` commit before it. CI checks out that `test:` commit and runs its feature files. They must fail there. CI then runs them at the head of the branch, where they must pass.
|
|
32
|
-
|
|
33
|
-
A requirement that turns out wrong while you build is not worked around. Change it in its own `spec:` commit, where the reviewer sees it.
|
|
34
|
-
|
|
35
|
-
Pull requests merge by rebase, so every commit lands on `main`. A `spec:` or `test:` commit is red by design. Skip it in `git bisect` with `git bisect skip`.
|
|
36
|
-
|
|
37
|
-
## Branches
|
|
38
|
-
|
|
39
|
-
A branch name is `<component>/<summary>` in lowercase kebab-case. The summary is two to four words about the whole change: `core/auth-refactoring`.
|
|
40
|
-
|
|
41
|
-
- The component is the package or plugin where the most important change lands, or `ci`, `docs`, `skills` or `deps`.
|
|
42
|
-
- Do not use generated names, random words, session IDs or dates.
|
|
43
|
-
- After a pull request merges, start follow-up work on a new branch.
|
|
44
|
-
|
|
45
|
-
## Feature files
|
|
46
|
-
|
|
47
|
-
Feature files live in `features/<domain>/<feature>.feature` inside the package or plugin they specify. Steps and the World live next to them, in `features/**/*.ts`.
|
|
48
|
-
|
|
49
|
-
- **One feature per file, with an `@id:<kebab-case>` tag.** Other files refer to the feature by this id. The id never changes after a push.
|
|
50
|
-
- **Every scenario is in a `Rule:`.** A rule groups the scenarios that show one business rule.
|
|
51
|
-
- **Write outcomes, not solutions.** Say what someone can do and what they see. Do not name screens, clicks, endpoints, status codes, wire formats or tables.
|
|
52
|
-
- **Write short scenarios.** Use five steps at most, with one `When` and one `Then`. A `Background:` has four steps at most.
|
|
53
|
-
- **Use real values from the World.** Name the World's things, not a category. Write `Given the instance "nas-01" has 3 pools`, not `Given an instance with pools`.
|
|
54
|
-
- **Write what someone can see in a `Then`.** Name what the admin, the agent or the upstream service observes, not an internal verdict.
|
|
55
|
-
- **Use the field's standard terms and the glossary's words.** Use each term with one meaning.
|
|
56
|
-
- **Tags.** Use only `@id:<kebab-case>`, `@backlog`, `@long-running` and `@exclusive`. `@backlog` marks a scenario not built yet, `@long-running` a slow scenario, and `@exclusive` a scenario that must run alone in its test process.
|
|
57
|
-
- **No comments.** Put an explanation in the feature narrative.
|
|
58
|
-
|
|
59
|
-
Scenarios run in vitest through quickpickle. They drive the product through its real interface: the MCP endpoints, the Admin API, the plugin entry or the portal in a browser. They never call internal services and never mock internal modules.
|
|
60
|
-
|
|
61
|
-
## Tests
|
|
62
|
-
|
|
63
|
-
Feature files are the only behavior tests. A new `*.test.ts` file must be a pure-function unit test in a `test/unit/` directory. Do not test libraries, frameworks or third-party APIs.
|
|
64
|
-
|
|
65
|
-
## Writing
|
|
66
|
-
|
|
67
|
-
All prose follows ASD-STE100 Simplified Technical English. This includes documents, skills, feature files and pull request descriptions.
|
|
68
|
-
|
|
69
|
-
- Write short sentences: 20 words at most for an instruction, 25 for a description.
|
|
70
|
-
- Write one instruction per sentence. Use the active voice and approved plain words.
|
|
71
|
-
- Do not use semicolons. Write two sentences.
|
|
72
|
-
- Do not use em dashes.
|
|
73
|
-
- Write one topic per paragraph.
|
|
74
|
-
- Do not repeat a fact. Link to the place that states it.
|
|
75
|
-
- Prefer tables and lists to long paragraphs.
|
|
76
|
-
|
|
77
|
-
## Security
|
|
78
|
-
|
|
79
|
-
- Do not commit credential files. The pre-commit hook refuses `.env` files, private keys and secret files by name. CI runs gitleaks on every pull request.
|
|
80
|
-
- A pull request that changes a security path needs a line of its own in its description: `Security audit: <boundary and findings>` or `Security audit: none. <why>`. The paths are in `synoikia.workflow.securityPaths` in the root `package.json`.
|
|
81
|
-
|
|
82
|
-
## Checks
|
|
83
|
-
|
|
84
|
-
Install the git hooks once per clone:
|
|
85
|
-
|
|
86
|
-
```sh
|
|
87
|
-
pnpm hooks:install
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
The hooks run the `workflow` script of the branch that you have checked out. Before you check out a pull request from someone you do not trust, read its changes to `.githooks/` and `package.json`.
|
|
91
|
-
|
|
92
|
-
| Command | Checks |
|
|
93
|
-
| ------------------------------------------ | ------------------------------------------------------------ |
|
|
94
|
-
| `pnpm workflow check-branch` | The branch name |
|
|
95
|
-
| `pnpm workflow check-phases --base main` | Commit prefixes and phases. `--red-green` runs the red check |
|
|
96
|
-
| `pnpm workflow check-features --base main` | Feature files, the slice size and new test files |
|
|
97
|
-
| `pnpm workflow check-prose --base main` | The writing rules on added lines |
|
|
98
|
-
| `pnpm workflow pre-push` | All of the above, as the pre-push hook runs them |
|
|
99
|
-
|
|
100
|
-
A pull request labelled `backfill` specifies behavior that exists already. Its scenarios pass at once, so CI skips the red check and the slice size for it.
|