mirror of
https://github.com/Permissionless-Software-Foundation/psf-memo.git
synced 2026-09-21 16:52:01 -07:00
Add canonical verify runner and verification record
Verification commands were transcribed in several places and the same full suite was re-run by every role. Add verify.sh/verify.mjs as the single per-component verification sequence emitting a machine-readable record (git_sha, per-command exit/duration/summary). The architect records it for each touched component; the specifier trusts a matching-SHA pass and only re-runs the merged feature's acceptance test. By specifier.
This commit is contained in:
@@ -51,6 +51,9 @@ You are the architect.
|
||||
## Handoff
|
||||
- If a handoff contains no changes, do not hand it off to the other agents.
|
||||
- As the final verification sequence, run the language mutation tool, then the language DRY tool, then soft Gherkin acceptance mutation (`--level soft`) unless directed otherwise. Fix any issues each tool finds before running the next one.
|
||||
- **Verification record**: before writing the review report, run the canonical verification runner for every component the task touched:
|
||||
`swarmforge/scripts/verify.sh <client|db|indexer> --record docs/reviews/<task>-verification.json --task <task>`
|
||||
This emits a machine-readable record (per-command exit, duration, summary) with the component `git_sha`. Commit it (force-add under the gitignored `docs/`) in the same commit as the review changes so the specifier can trust it without re-running the full suite.
|
||||
- **Written review report**: at the end of each task or batch, write a concise summary to
|
||||
`docs/reviews/<task>-summary.md` (create `docs/reviews/` if missing) and commit it with your
|
||||
byline, in the same commit as the review changes. The summary is the durable written record
|
||||
|
||||
@@ -24,6 +24,7 @@ You are the specifier.
|
||||
## Verification
|
||||
- Do not run Gherkin acceptance mutation.
|
||||
- Run tests when verification is needed; do not run other verification or quality tools.
|
||||
- After merging the architect branch, check `docs/reviews/<task>-verification.json`. It must exist and its `git_sha` must match the merged commit. When it matches and reports `pass`, do not re-run the full suite; run only the acceptance test for the merged feature as an independent check. If the record is missing, stale, or reports a failure, run `swarmforge/scripts/verify.sh` for the affected component(s).
|
||||
|
||||
## Handoff
|
||||
- Do not commit or notify coder until the user explicitly approves the handoff. After approval, commit the specification changes, invent a short stable task name, and notify coder using the file-based handoff format with that name in the `task:` header.
|
||||
|
||||
Executable
+164
@@ -0,0 +1,164 @@
|
||||
#!/usr/bin/env node
|
||||
/*
|
||||
Canonical per-component verification runner for psf-memo.
|
||||
|
||||
Runs the standard verification sequence for one component (unit, property,
|
||||
acceptance, lint, and build for the client) and emits a machine-readable
|
||||
JSON record. This is the single source of truth for "what does verification
|
||||
mean for this component", so roles do not have to transcribe commands.
|
||||
|
||||
Usage:
|
||||
node swarmforge/scripts/verify.mjs <client|db|indexer>
|
||||
node swarmforge/scripts/verify.mjs <component> --record <file> --task <name>
|
||||
|
||||
Exit codes:
|
||||
0 every command passed
|
||||
1 at least one command failed
|
||||
2 usage error
|
||||
*/
|
||||
|
||||
import { execFileSync } from 'node:child_process'
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||
const repoRoot = path.resolve(__dirname, '..', '..')
|
||||
|
||||
const COMPONENTS = {
|
||||
client: {
|
||||
dir: 'psf-memo-client',
|
||||
commands: [
|
||||
['unit', ['test']],
|
||||
['property', ['run', 'test:property']],
|
||||
['acceptance', ['run', 'test:acceptance']],
|
||||
['lint', ['run', 'lint']],
|
||||
['build', ['run', 'build']]
|
||||
]
|
||||
},
|
||||
db: {
|
||||
dir: 'psf-memo-db',
|
||||
commands: [
|
||||
['unit', ['test']],
|
||||
['property', ['run', 'property']],
|
||||
['acceptance', ['run', 'acceptance']],
|
||||
['lint', ['run', 'lint']]
|
||||
]
|
||||
},
|
||||
indexer: {
|
||||
dir: 'psf-memo-indexer',
|
||||
commands: [
|
||||
['unit', ['test']],
|
||||
['property', ['run', 'property']],
|
||||
['acceptance', ['run', 'acceptance']],
|
||||
['lint', ['run', 'lint']]
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
function usage () {
|
||||
console.error('usage: verify.mjs <client|db|indexer> [--record <file>] [--task <name>]')
|
||||
}
|
||||
|
||||
function git (args) {
|
||||
try {
|
||||
return execFileSync('git', args, { cwd: repoRoot, encoding: 'utf8' }).trim()
|
||||
} catch (err) {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
function summarize (name, out, ok) {
|
||||
const clean = out.replace(/\x1b\[[0-9;]*m/g, '')
|
||||
if (!ok) {
|
||||
const lines = clean.trim().split('\n')
|
||||
const tail = lines.slice(-8).join(' | ').replace(/\s+/g, ' ').slice(0, 400)
|
||||
return `FAILED: ${tail}`
|
||||
}
|
||||
let m = clean.match(/(\d+) passing/)
|
||||
if (m) return `${m[1]} passing`
|
||||
m = clean.match(/# pass (\d+)/)
|
||||
if (m) {
|
||||
const fail = clean.match(/# fail (\d+)/)
|
||||
return `${m[1]} pass / ${fail ? fail[1] : 0} fail`
|
||||
}
|
||||
m = clean.match(/ACCEPTANCE: all (\d+) generated test file\(s\) passed/)
|
||||
if (m) return `all ${m[1]} acceptance suites passed`
|
||||
return 'ok'
|
||||
}
|
||||
|
||||
function runCommand (dir, name, args) {
|
||||
const started = Date.now()
|
||||
let out = ''
|
||||
let exit = 0
|
||||
try {
|
||||
out = execFileSync('npm', args, {
|
||||
cwd: dir,
|
||||
encoding: 'utf8',
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
env: process.env
|
||||
})
|
||||
} catch (err) {
|
||||
exit = typeof err.status === 'number' ? err.status : 1
|
||||
out = `${err.stdout || ''}\n${err.stderr || ''}`
|
||||
}
|
||||
const ok = exit === 0
|
||||
process.stderr.write(` [${ok ? 'ok' : 'FAIL'}] ${name} (${Date.now() - started}ms)\n`)
|
||||
return {
|
||||
name,
|
||||
command: `npm ${args.join(' ')}`,
|
||||
exit,
|
||||
duration_ms: Date.now() - started,
|
||||
summary: summarize(name, out, ok)
|
||||
}
|
||||
}
|
||||
|
||||
function main () {
|
||||
const argv = process.argv.slice(2)
|
||||
const name = argv[0]
|
||||
if (!name || !COMPONENTS[name]) {
|
||||
usage()
|
||||
process.exit(2)
|
||||
}
|
||||
let recordPath = ''
|
||||
let task = ''
|
||||
for (let i = 1; i < argv.length; i++) {
|
||||
if (argv[i] === '--record') recordPath = argv[++i] || ''
|
||||
else if (argv[i] === '--task') task = argv[++i] || ''
|
||||
else {
|
||||
usage()
|
||||
process.exit(2)
|
||||
}
|
||||
}
|
||||
|
||||
const spec = COMPONENTS[name]
|
||||
const componentDir = path.join(repoRoot, spec.dir)
|
||||
process.stderr.write(`== verify ${spec.dir} ==\n`)
|
||||
|
||||
const commands = spec.commands.map(([cmdName, args]) => runCommand(componentDir, cmdName, args))
|
||||
const failed = commands.filter((c) => c.exit !== 0)
|
||||
const record = {
|
||||
schema_version: 1,
|
||||
task: task || null,
|
||||
component: spec.dir,
|
||||
git_sha: git(['rev-parse', 'HEAD']),
|
||||
branch: git(['rev-parse', '--abbrev-ref', 'HEAD']),
|
||||
timestamp: new Date().toISOString(),
|
||||
commands,
|
||||
result: failed.length === 0 ? 'pass' : 'fail'
|
||||
}
|
||||
|
||||
const json = `${JSON.stringify(record, null, 2)}\n`
|
||||
if (recordPath) {
|
||||
fs.mkdirSync(path.dirname(recordPath), { recursive: true })
|
||||
fs.writeFileSync(recordPath, json)
|
||||
process.stderr.write(`verification record: ${recordPath}\n`)
|
||||
} else {
|
||||
process.stdout.write(json)
|
||||
}
|
||||
|
||||
process.stderr.write(`result: ${record.result} (${commands.length - failed.length}/${commands.length} commands passed)\n`)
|
||||
process.exit(failed.length === 0 ? 0 : 1)
|
||||
}
|
||||
|
||||
main()
|
||||
Executable
+6
@@ -0,0 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
# Thin wrapper for the canonical per-component verification runner.
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
exec node "$SCRIPT_DIR/verify.mjs" "$@"
|
||||
Reference in New Issue
Block a user