bigpowers 2.88.16 → 2.88.18
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/.gemini/extensions/bigpowers/gemini-extension.json +1 -1
- package/.mcp.json +2 -2
- package/.pi/package.json +1 -1
- package/CHANGELOG.md +18 -0
- package/bigpowers-mcp/build/lib/git-context.js +52 -36
- package/bigpowers-mcp/build/lib/git-context.js.map +1 -1
- package/bigpowers-mcp/src/lib/git-context.ts +9 -5
- package/dashboard/src/web/client.html +15 -8
- package/dashboard/src/web/server.js +2 -1
- package/package.json +1 -1
- package/scripts/bp-read-agents.sh +7 -5
- package/scripts/lib/__pycache__/link_utils.cpython-310.pyc +0 -0
- package/scripts/lib/__pycache__/srp_engine_links.cpython-310.pyc +0 -0
- package/scripts/lib/__pycache__/srp_engine_okf.cpython-310.pyc +0 -0
- package/scripts/lib/golden-suite-gates.sh +6 -0
- package/scripts/lib/migrate-version-common.sh +15 -5
- package/scripts/lib/migrate-version-plan.sh +7 -7
- package/scripts/lib/migrate-version-run.sh +3 -0
- package/scripts/lib/srp-engine.py +1 -60
- package/scripts/lib/srp_engine_okf.py +65 -0
- package/scripts/lib/story-verify-memo.sh +56 -0
- package/scripts/lib/test-assertions.sh +6 -0
- package/scripts/mcp-smoke.ts +48 -43
- package/scripts/run-story-verify.sh +3 -50
- package/scripts/test-golden-gate-runner.sh +18 -18
- package/scripts/test-project-runtime.sh +10 -19
- package/scripts/validate-docs-base-path.sh +15 -22
- package/scripts/validate-mcp-package.sh +18 -20
- package/scripts/validate-omp-extension.sh +23 -30
- package/tests/security/agents-import.cjs +36 -0
- package/tests/security/dashboard-bind.cjs +31 -0
- package/tests/security/dashboard-render.cjs +60 -0
- package/tests/security/git-context.cjs +44 -0
- package/tests/security/migration.cjs +76 -0
package/.mcp.json
CHANGED
package/.pi/package.json
CHANGED
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,21 @@
|
|
|
1
|
+
## [2.88.18](https://github.com/danielvm-git/bigpowers/compare/v2.88.17...v2.88.18) (2026-11-02)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* **dashboard:** bind the project dashboard to loopback ([#175](https://github.com/danielvm-git/bigpowers/issues/175)) ([b796b17](https://github.com/danielvm-git/bigpowers/commit/b796b17f3b077a36d725018d3ad6f2fb5cc0426f))
|
|
7
|
+
* **dashboard:** escape project metadata before rendering HTML ([#178](https://github.com/danielvm-git/bigpowers/issues/178)) ([f905d5b](https://github.com/danielvm-git/bigpowers/commit/f905d5b30753e715d8675401aeceefa98d850476))
|
|
8
|
+
* **mcp:** filter every diff before returning its contents ([#174](https://github.com/danielvm-git/bigpowers/issues/174)) ([d0b9f70](https://github.com/danielvm-git/bigpowers/commit/d0b9f70a7319d396858890e2abfa1253b68837ff))
|
|
9
|
+
* **migrate:** treat version metadata and preview paths as data ([#177](https://github.com/danielvm-git/bigpowers/issues/177)) ([2b0649c](https://github.com/danielvm-git/bigpowers/commit/2b0649c0a8723695e2c1f078112bca3993b8b383))
|
|
10
|
+
* **scripts:** escape AGENTS commands when emitting Bash exports ([#176](https://github.com/danielvm-git/bigpowers/issues/176)) ([1800dd5](https://github.com/danielvm-git/bigpowers/commit/1800dd5f682cb98afdbbdce06cbcc8cae2a6eb9f))
|
|
11
|
+
|
|
12
|
+
## [2.88.17](https://github.com/danielvm-git/bigpowers/compare/v2.88.16...v2.88.17) (2026-10-30)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### Bug Fixes
|
|
16
|
+
|
|
17
|
+
* **compliance:** restore akita.feature to 0 FAIL — dedupe helpers, split oversized files ([#171](https://github.com/danielvm-git/bigpowers/issues/171)) ([4ed9a26](https://github.com/danielvm-git/bigpowers/commit/4ed9a2663c39689de04fd2ff796b09777a78cfcf)), closes [#118](https://github.com/danielvm-git/bigpowers/issues/118) [123/#125](https://github.com/danielvm-git/bigpowers/issues/125) [#161](https://github.com/danielvm-git/bigpowers/issues/161) [#155](https://github.com/danielvm-git/bigpowers/issues/155) [#161](https://github.com/danielvm-git/bigpowers/issues/161) [#168](https://github.com/danielvm-git/bigpowers/issues/168) [#169](https://github.com/danielvm-git/bigpowers/issues/169) [#168](https://github.com/danielvm-git/bigpowers/issues/168) [#168](https://github.com/danielvm-git/bigpowers/issues/168)
|
|
18
|
+
|
|
1
19
|
## [2.88.16](https://github.com/danielvm-git/bigpowers/compare/v2.88.15...v2.88.16) (2026-10-25)
|
|
2
20
|
|
|
3
21
|
|
|
@@ -6,17 +6,15 @@ import { GIT_SCOPE_DIRS } from "../config.js";
|
|
|
6
6
|
import { isSecretPath } from "./paths.js";
|
|
7
7
|
function inScope(filePath) {
|
|
8
8
|
const norm = filePath.replace(/\\/g, "/");
|
|
9
|
-
if (isSecretPath(norm))
|
|
10
|
-
|
|
11
|
-
return GIT_SCOPE_DIRS.some((d) => norm === d || norm.startsWith(`${d}/`));
|
|
9
|
+
if (isSecretPath(norm)) return false;
|
|
10
|
+
return GIT_SCOPE_DIRS.some((d)=>norm === d || norm.startsWith(`${d}/`));
|
|
12
11
|
}
|
|
13
12
|
function groupByDir(files) {
|
|
14
13
|
const grouped = {};
|
|
15
|
-
for (const f of files)
|
|
14
|
+
for (const f of files){
|
|
16
15
|
const parts = f.split("/");
|
|
17
16
|
const dir = parts.length > 1 ? parts[0] : ".";
|
|
18
|
-
if (!grouped[dir])
|
|
19
|
-
grouped[dir] = [];
|
|
17
|
+
if (!grouped[dir]) grouped[dir] = [];
|
|
20
18
|
grouped[dir].push(f);
|
|
21
19
|
}
|
|
22
20
|
return grouped;
|
|
@@ -25,18 +23,20 @@ function git(repoRoot, args) {
|
|
|
25
23
|
return execFileSync("git", args, {
|
|
26
24
|
cwd: repoRoot,
|
|
27
25
|
encoding: "utf8",
|
|
28
|
-
maxBuffer: 10 * 1024 * 1024
|
|
26
|
+
maxBuffer: 10 * 1024 * 1024
|
|
29
27
|
}).trim();
|
|
30
28
|
}
|
|
31
29
|
export function getGitContext(repoRoot, action) {
|
|
32
30
|
if (action === "status") {
|
|
33
|
-
const out = git(repoRoot, [
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
31
|
+
const out = git(repoRoot, [
|
|
32
|
+
"status",
|
|
33
|
+
"--porcelain"
|
|
34
|
+
]);
|
|
35
|
+
const files = out.split("\n").filter(Boolean).map((line)=>line.slice(3).trim()).filter(inScope);
|
|
36
|
+
return {
|
|
37
|
+
action,
|
|
38
|
+
changed_files: groupByDir(files)
|
|
39
|
+
};
|
|
40
40
|
}
|
|
41
41
|
if (action === "log") {
|
|
42
42
|
const out = git(repoRoot, [
|
|
@@ -45,48 +45,64 @@ export function getGitContext(repoRoot, action) {
|
|
|
45
45
|
"--pretty=format:%H|%s",
|
|
46
46
|
"--name-only",
|
|
47
47
|
"--",
|
|
48
|
-
...GIT_SCOPE_DIRS
|
|
48
|
+
...GIT_SCOPE_DIRS
|
|
49
49
|
]);
|
|
50
50
|
const commits = [];
|
|
51
51
|
let current = null;
|
|
52
|
-
for (const line of out.split("\n"))
|
|
52
|
+
for (const line of out.split("\n")){
|
|
53
53
|
if (line.includes("|")) {
|
|
54
|
-
if (current)
|
|
55
|
-
commits.push(current);
|
|
54
|
+
if (current) commits.push(current);
|
|
56
55
|
const [hash, subject] = line.split("|");
|
|
57
|
-
current = {
|
|
58
|
-
|
|
59
|
-
|
|
56
|
+
current = {
|
|
57
|
+
hash,
|
|
58
|
+
subject,
|
|
59
|
+
files: []
|
|
60
|
+
};
|
|
61
|
+
} else if (line.trim() && current && inScope(line.trim())) {
|
|
60
62
|
current.files.push(line.trim());
|
|
61
63
|
}
|
|
62
64
|
}
|
|
63
|
-
if (current)
|
|
64
|
-
|
|
65
|
-
|
|
65
|
+
if (current) commits.push(current);
|
|
66
|
+
return {
|
|
67
|
+
action,
|
|
68
|
+
changed_files: {},
|
|
69
|
+
commits
|
|
70
|
+
};
|
|
66
71
|
}
|
|
67
|
-
const diffOut = git(repoRoot, [
|
|
72
|
+
const diffOut = git(repoRoot, [
|
|
73
|
+
"diff",
|
|
74
|
+
"--",
|
|
75
|
+
...GIT_SCOPE_DIRS
|
|
76
|
+
]);
|
|
68
77
|
const diffs = {};
|
|
69
78
|
let currentFile = "";
|
|
70
79
|
let buf = [];
|
|
71
|
-
|
|
80
|
+
const flushDiff = ()=>{
|
|
81
|
+
if (currentFile && buf.length && inScope(currentFile)) {
|
|
82
|
+
diffs[currentFile] = buf.join("\n");
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
for (const line of diffOut.split("\n")){
|
|
72
86
|
if (line.startsWith("diff --git")) {
|
|
73
|
-
|
|
74
|
-
diffs[currentFile] = buf.join("\n");
|
|
87
|
+
flushDiff();
|
|
75
88
|
const match = / b\/(.+)$/.exec(line);
|
|
76
89
|
currentFile = match?.[1] ?? "";
|
|
77
|
-
buf = [
|
|
78
|
-
|
|
79
|
-
|
|
90
|
+
buf = [
|
|
91
|
+
line
|
|
92
|
+
];
|
|
93
|
+
} else {
|
|
80
94
|
buf.push(line);
|
|
81
95
|
}
|
|
82
96
|
}
|
|
83
|
-
|
|
84
|
-
diffs[currentFile] = buf.join("\n");
|
|
85
|
-
}
|
|
97
|
+
flushDiff();
|
|
86
98
|
const changed = Object.keys(diffs);
|
|
87
|
-
return {
|
|
99
|
+
return {
|
|
100
|
+
action,
|
|
101
|
+
changed_files: groupByDir(changed),
|
|
102
|
+
diffs
|
|
103
|
+
};
|
|
88
104
|
}
|
|
89
105
|
export function isGitRepo(repoRoot) {
|
|
90
106
|
return fs.existsSync(path.join(repoRoot, ".git"));
|
|
91
107
|
}
|
|
92
|
-
//# sourceMappingURL=git-context.js.map
|
|
108
|
+
//# sourceMappingURL=git-context.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"
|
|
1
|
+
{"version":3,"sources":["../../src/lib/git-context.ts"],"names":[],"mappings":"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","file":"git-context.js","sourceRoot":""}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { execFileSync } from "node:child_process";
|
|
3
3
|
import fs from "node:fs";
|
|
4
4
|
import path from "node:path";
|
|
5
|
-
import { GIT_SCOPE_DIRS
|
|
5
|
+
import { GIT_SCOPE_DIRS } from "../config.js";
|
|
6
6
|
import { isSecretPath } from "./paths.js";
|
|
7
7
|
|
|
8
8
|
export interface GitContextResult {
|
|
@@ -76,9 +76,14 @@ export function getGitContext(repoRoot: string, action: "status" | "log" | "diff
|
|
|
76
76
|
const diffs: Record<string, string> = {};
|
|
77
77
|
let currentFile = "";
|
|
78
78
|
let buf: string[] = [];
|
|
79
|
+
const flushDiff = () => {
|
|
80
|
+
if (currentFile && buf.length && inScope(currentFile)) {
|
|
81
|
+
diffs[currentFile] = buf.join("\n");
|
|
82
|
+
}
|
|
83
|
+
};
|
|
79
84
|
for (const line of diffOut.split("\n")) {
|
|
80
85
|
if (line.startsWith("diff --git")) {
|
|
81
|
-
|
|
86
|
+
flushDiff();
|
|
82
87
|
const match = / b\/(.+)$/.exec(line);
|
|
83
88
|
currentFile = match?.[1] ?? "";
|
|
84
89
|
buf = [line];
|
|
@@ -86,9 +91,7 @@ export function getGitContext(repoRoot: string, action: "status" | "log" | "diff
|
|
|
86
91
|
buf.push(line);
|
|
87
92
|
}
|
|
88
93
|
}
|
|
89
|
-
|
|
90
|
-
diffs[currentFile] = buf.join("\n");
|
|
91
|
-
}
|
|
94
|
+
flushDiff();
|
|
92
95
|
|
|
93
96
|
const changed = Object.keys(diffs);
|
|
94
97
|
return { action, changed_files: groupByDir(changed), diffs };
|
|
@@ -97,3 +100,4 @@ export function getGitContext(repoRoot: string, action: "status" | "log" | "diff
|
|
|
97
100
|
export function isGitRepo(repoRoot: string): boolean {
|
|
98
101
|
return fs.existsSync(path.join(repoRoot, ".git"));
|
|
99
102
|
}
|
|
103
|
+
|
|
@@ -218,6 +218,12 @@
|
|
|
218
218
|
let eventSource = null;
|
|
219
219
|
let pollInterval = null;
|
|
220
220
|
|
|
221
|
+
// Project metadata belongs in HTML text content, never in markup.
|
|
222
|
+
function escapeHtml(value) {
|
|
223
|
+
const entities = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' };
|
|
224
|
+
return String(value).replace(/[&<>"']/g, char => entities[char]);
|
|
225
|
+
}
|
|
226
|
+
|
|
221
227
|
function render(data) {
|
|
222
228
|
if (!data) return;
|
|
223
229
|
renderPipeline(data);
|
|
@@ -246,14 +252,14 @@
|
|
|
246
252
|
|
|
247
253
|
let html = '';
|
|
248
254
|
if (metrics) {
|
|
249
|
-
html += `<div class="metric-row"><span class="metric-label">Total BCPs</span><span class="metric-value">${metrics.totalBcps}</span></div>`;
|
|
255
|
+
html += `<div class="metric-row"><span class="metric-label">Total BCPs</span><span class="metric-value">${escapeHtml(metrics.totalBcps)}</span></div>`;
|
|
250
256
|
html += `<div class="metric-row"><span class="metric-label">Avg BCP/Hour</span><span class="metric-value">${metrics.avgBcpPerHour.toFixed(2)}</span></div>`;
|
|
251
257
|
}
|
|
252
258
|
if (velocity) {
|
|
253
259
|
html += `<div class="metric-row"><span class="metric-label">Current Velocity</span><span class="metric-value">${velocity.avgBcpPerHour.toFixed(2)} BCP/h</span></div>`;
|
|
254
260
|
}
|
|
255
261
|
if (data.state?.release?.version) {
|
|
256
|
-
html += `<div class="metric-row"><span class="metric-label">Version</span><span class="metric-value">${data.state.release.version}</span></div>`;
|
|
262
|
+
html += `<div class="metric-row"><span class="metric-label">Version</span><span class="metric-value">${escapeHtml(data.state.release.version)}</span></div>`;
|
|
257
263
|
}
|
|
258
264
|
if (timestamp) {
|
|
259
265
|
html += `<div class="metric-row"><span class="metric-label">Updated</span><span class="metric-value" style="font-size: 10px;">${new Date(timestamp).toLocaleTimeString()}</span></div>`;
|
|
@@ -271,7 +277,7 @@
|
|
|
271
277
|
{ key: 'Git Branch', value: state.gitBranch }
|
|
272
278
|
];
|
|
273
279
|
container.innerHTML = pairs.map(pair => {
|
|
274
|
-
const val = pair.value || '(none)';
|
|
280
|
+
const val = escapeHtml(pair.value || '(none)');
|
|
275
281
|
return `<div class="state-pair"><span class="state-key">${pair.key}</span><span class="state-value">${val}</span></div>`;
|
|
276
282
|
}).join('');
|
|
277
283
|
}
|
|
@@ -288,8 +294,8 @@
|
|
|
288
294
|
const storyCount = stories.length;
|
|
289
295
|
return `
|
|
290
296
|
<div class="epic-item">
|
|
291
|
-
<div class="epic-title">${epic.id || 'Unknown'}</div>
|
|
292
|
-
<div style="color: var(--ink-soft); font-size: 11px; margin-bottom: 0;">${epic.title || 'Untitled'}</div>
|
|
297
|
+
<div class="epic-title">${escapeHtml(epic.id || 'Unknown')}</div>
|
|
298
|
+
<div style="color: var(--ink-soft); font-size: 11px; margin-bottom: 0;">${escapeHtml(epic.title || 'Untitled')}</div>
|
|
293
299
|
<div class="epic-stories">${storyCount} stories</div>
|
|
294
300
|
</div>
|
|
295
301
|
`;
|
|
@@ -308,10 +314,10 @@
|
|
|
308
314
|
const end = row.end ? new Date(row.end).toLocaleDateString() : '(active)';
|
|
309
315
|
return `
|
|
310
316
|
<div class="ledger-row">
|
|
311
|
-
<div class="ledger-id">${row.id}</div>
|
|
317
|
+
<div class="ledger-id">${escapeHtml(row.id)}</div>
|
|
312
318
|
<div style="text-align: right;">
|
|
313
|
-
<div class="ledger-value">${row.bcps} BCP</div>
|
|
314
|
-
<div class="ledger-meta">${row.cycleMin}min | ${row.bcpPerHour.toFixed(2)}/h</div>
|
|
319
|
+
<div class="ledger-value">${escapeHtml(row.bcps)} BCP</div>
|
|
320
|
+
<div class="ledger-meta">${escapeHtml(row.cycleMin)}min | ${row.bcpPerHour.toFixed(2)}/h</div>
|
|
315
321
|
</div>
|
|
316
322
|
</div>
|
|
317
323
|
`;
|
|
@@ -417,3 +423,4 @@
|
|
|
417
423
|
</script>
|
|
418
424
|
</body>
|
|
419
425
|
</html>
|
|
426
|
+
|
|
@@ -104,9 +104,10 @@ function createServer(projectRoot, port = 7742) {
|
|
|
104
104
|
|
|
105
105
|
return {
|
|
106
106
|
listen(portNum = port, cb) {
|
|
107
|
-
return app.listen(portNum, cb);
|
|
107
|
+
return app.listen(portNum, '127.0.0.1', cb);
|
|
108
108
|
}
|
|
109
109
|
};
|
|
110
110
|
}
|
|
111
111
|
|
|
112
112
|
module.exports = { createServer };
|
|
113
|
+
|
package/package.json
CHANGED
|
@@ -53,9 +53,11 @@ if $PRINT_MODE; then
|
|
|
53
53
|
if [ -n "$BP_LINT" ]; then echo "Lint: $BP_LINT"; fi
|
|
54
54
|
if [ -n "$BP_DEPLOY" ]; then echo "Deploy: $BP_DEPLOY"; fi
|
|
55
55
|
else
|
|
56
|
-
|
|
57
|
-
if [ -n "$
|
|
58
|
-
if [ -n "$
|
|
59
|
-
if [ -n "$
|
|
60
|
-
if [ -n "$
|
|
56
|
+
# Bash %q preserves the command as data when the caller evaluates exports.
|
|
57
|
+
if [ -n "$BP_PREFLIGHT" ]; then printf 'export BP_PREFLIGHT=%q\n' "$BP_PREFLIGHT"; fi
|
|
58
|
+
if [ -n "$BP_TEST" ]; then printf 'export BP_TEST=%q\n' "$BP_TEST"; fi
|
|
59
|
+
if [ -n "$BP_BUILD" ]; then printf 'export BP_BUILD=%q\n' "$BP_BUILD"; fi
|
|
60
|
+
if [ -n "$BP_LINT" ]; then printf 'export BP_LINT=%q\n' "$BP_LINT"; fi
|
|
61
|
+
if [ -n "$BP_DEPLOY" ]; then printf 'export BP_DEPLOY=%q\n' "$BP_DEPLOY"; fi
|
|
61
62
|
fi
|
|
63
|
+
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -9,6 +9,7 @@ GOLDEN_GATES=(
|
|
|
9
9
|
"compliance:npm run compliance:false"
|
|
10
10
|
"g04-selftest:bash scripts/golden-g04-selftest.sh:true"
|
|
11
11
|
"g06-migrate-version:bash tests/run-golden-migrate-version.sh:true"
|
|
12
|
+
"security-migration:node tests/security/migration.cjs:false"
|
|
12
13
|
"g07-negative-path:bash scripts/golden-g07-negative-path.sh:false"
|
|
13
14
|
"g08-anti-vacuity:bash scripts/golden-g08-anti-vacuity.sh:false"
|
|
14
15
|
"g09-yaml-roundtrip:bash scripts/golden-g09-yaml-roundtrip.sh:false"
|
|
@@ -66,10 +67,14 @@ GOLDEN_GATES=(
|
|
|
66
67
|
"golden-g11-worktree-venv:bash scripts/test-golden-g11-worktree-venv.sh:false"
|
|
67
68
|
"install-helpers:bash scripts/test-install-helpers.sh:false"
|
|
68
69
|
"project-runtime:bash scripts/test-project-runtime.sh:false"
|
|
70
|
+
"security-agents-import:node tests/security/agents-import.cjs:false"
|
|
69
71
|
"omp-extension:bash scripts/validate-omp-extension.sh:false"
|
|
70
72
|
"mcp-package:bash scripts/validate-mcp-package.sh:false"
|
|
73
|
+
"security-git-context:node tests/security/git-context.cjs:false"
|
|
71
74
|
"docs-base-path:bash scripts/validate-docs-base-path.sh:false"
|
|
75
|
+
"security-dashboard-bind:node tests/security/dashboard-bind.cjs:false"
|
|
72
76
|
"import-boundaries:bash scripts/check-import-boundaries.sh:false"
|
|
77
|
+
"security-dashboard-render:node tests/security/dashboard-render.cjs:false"
|
|
73
78
|
"skill-catalog:bash scripts/validate-skill-catalog.sh:false"
|
|
74
79
|
"specs-parse:bash scripts/validate-specs-yaml.sh:false"
|
|
75
80
|
# The runner itself: failing output is shown, skips are named and reasoned.
|
|
@@ -204,3 +209,4 @@ golden_run_deterministic_gates() {
|
|
|
204
209
|
fi
|
|
205
210
|
done
|
|
206
211
|
}
|
|
212
|
+
|
|
@@ -21,23 +21,32 @@ EOF
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
migrate_installed_version() {
|
|
24
|
-
node -e
|
|
24
|
+
node -e 'console.log(require(process.argv[1]).version)' "$BIGPOWERS_ROOT/package.json" 2>/dev/null || echo "unknown"
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
migrate_validate_version() {
|
|
28
|
+
local version="$1" label="$2"
|
|
29
|
+
local pattern='^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?(\+[0-9A-Za-z.-]+)?$'
|
|
30
|
+
if [[ ! "$version" =~ $pattern ]]; then
|
|
31
|
+
echo "migrate-version: invalid $label version; expected major.minor.patch with optional prerelease/build metadata" >&2
|
|
32
|
+
return 4
|
|
33
|
+
fi
|
|
25
34
|
}
|
|
26
35
|
|
|
27
36
|
migrate_yaml_get() {
|
|
28
37
|
local file="$1" key="$2"
|
|
29
|
-
$PYTHON -c "
|
|
38
|
+
"$PYTHON" -c "
|
|
30
39
|
import yaml, sys
|
|
31
40
|
try:
|
|
32
|
-
d = yaml.safe_load(open(
|
|
41
|
+
d = yaml.safe_load(open(sys.argv[1]))
|
|
33
42
|
val = d
|
|
34
|
-
for k in
|
|
43
|
+
for k in sys.argv[2].split('.'):
|
|
35
44
|
if not isinstance(val, dict): sys.exit(1)
|
|
36
45
|
val = val.get(k)
|
|
37
46
|
if val is None: sys.exit(1)
|
|
38
47
|
print(val)
|
|
39
48
|
except: sys.exit(1)
|
|
40
|
-
" 2>/dev/null
|
|
49
|
+
" "$file" "$key" 2>/dev/null
|
|
41
50
|
}
|
|
42
51
|
|
|
43
52
|
log_info() { echo " [INFO] $*"; }
|
|
@@ -111,3 +120,4 @@ migrate_detect_version() {
|
|
|
111
120
|
fi
|
|
112
121
|
return 0
|
|
113
122
|
}
|
|
123
|
+
|
|
@@ -8,15 +8,14 @@ MIGRATE_VERSION_PLAN_LOADED=1
|
|
|
8
8
|
migrate_build_plan() {
|
|
9
9
|
echo "migrate-version: building migration plan from $REGISTRY …"
|
|
10
10
|
|
|
11
|
-
MIGRATION_PLAN="$($PYTHON -c "
|
|
11
|
+
MIGRATION_PLAN="$("$PYTHON" -c "
|
|
12
12
|
import yaml, json, sys
|
|
13
13
|
|
|
14
|
-
with open(
|
|
14
|
+
with open(sys.argv[1]) as f:
|
|
15
15
|
parts = f.read().split('---')
|
|
16
16
|
fm = yaml.safe_load(parts[1])
|
|
17
17
|
|
|
18
|
-
detected =
|
|
19
|
-
installed = '$INSTALLED'
|
|
18
|
+
detected, installed = sys.argv[2:4]
|
|
20
19
|
|
|
21
20
|
plan = []
|
|
22
21
|
for m in sorted(fm.get('migrations', []), key=lambda x: x.get('order', 99)):
|
|
@@ -30,7 +29,7 @@ for m in sorted(fm.get('migrations', []), key=lambda x: x.get('order', 99)):
|
|
|
30
29
|
|
|
31
30
|
applicable = [m for m in plan if m['since_version'] > detected and m['since_version'] <= installed]
|
|
32
31
|
print(json.dumps(applicable))
|
|
33
|
-
"
|
|
32
|
+
" "$REGISTRY" "$DETECTED" "$INSTALLED")"
|
|
34
33
|
|
|
35
34
|
MIGRATION_COUNT="$(echo "$MIGRATION_PLAN" | $PYTHON -c "import json,sys; print(len(json.load(sys.stdin)))" 2>/dev/null)"
|
|
36
35
|
|
|
@@ -79,7 +78,7 @@ for m in json.load(sys.stdin):
|
|
|
79
78
|
echo "$MIGRATION_PLAN" | $PYTHON -c "
|
|
80
79
|
import json, sys, os
|
|
81
80
|
for m in json.load(sys.stdin):
|
|
82
|
-
bundle_path = os.path.join(
|
|
81
|
+
bundle_path = os.path.join(sys.argv[1], m['file'])
|
|
83
82
|
if not os.path.exists(bundle_path):
|
|
84
83
|
print(f\" WARN {m['id']}: bundle file not found ({m['file']})\")
|
|
85
84
|
continue
|
|
@@ -112,8 +111,9 @@ for m in json.load(sys.stdin):
|
|
|
112
111
|
for a in fm.get('actions_needed', []):
|
|
113
112
|
v = a.get('verify', '')
|
|
114
113
|
if v: print(f\" verify: {v}\")
|
|
115
|
-
" 2>/dev/null
|
|
114
|
+
" "$MIGRATIONS_DIR" 2>/dev/null
|
|
116
115
|
|
|
117
116
|
echo ""
|
|
118
117
|
echo "Run without --dry-run to apply these migrations."
|
|
119
118
|
}
|
|
119
|
+
|
|
@@ -28,6 +28,8 @@ migrate_version_main() {
|
|
|
28
28
|
|
|
29
29
|
INSTALLED="$(migrate_installed_version)"
|
|
30
30
|
migrate_detect_version || exit $?
|
|
31
|
+
migrate_validate_version "$DETECTED" detected || exit $?
|
|
32
|
+
migrate_validate_version "$INSTALLED" installed || exit $?
|
|
31
33
|
|
|
32
34
|
echo "migrate-version: detected=$DETECTED installed=$INSTALLED"
|
|
33
35
|
|
|
@@ -51,3 +53,4 @@ migrate_version_main() {
|
|
|
51
53
|
migrate_finalize
|
|
52
54
|
exit 0
|
|
53
55
|
}
|
|
56
|
+
|
|
@@ -17,6 +17,7 @@ if _LIB_DIR not in sys.path:
|
|
|
17
17
|
|
|
18
18
|
from link_utils import LINK_RE, EXTERNAL_RE # noqa: E402
|
|
19
19
|
from srp_engine_links import rewrite_links_for_pi # noqa: E402
|
|
20
|
+
from srp_engine_okf import render_okf_concept # noqa: E402
|
|
20
21
|
|
|
21
22
|
# End-user setup (bigpowers setup) may run on a python3 without PyYAML.
|
|
22
23
|
# Prefer PyYAML for exact parsing; fall back to the dependency-free
|
|
@@ -133,66 +134,6 @@ def iterate_skills(repo_root):
|
|
|
133
134
|
skills.append(skill_md)
|
|
134
135
|
return sorted(skills)
|
|
135
136
|
|
|
136
|
-
def render_okf_concept(skill_data, okf_wiki_skills):
|
|
137
|
-
name = skill_data['name']
|
|
138
|
-
model = skill_data['model']
|
|
139
|
-
effort = skill_data['effort']
|
|
140
|
-
description = skill_data['description']
|
|
141
|
-
|
|
142
|
-
os.makedirs(okf_wiki_skills, exist_ok=True)
|
|
143
|
-
okf_file = os.path.join(okf_wiki_skills, f"{name}.md")
|
|
144
|
-
|
|
145
|
-
phase = "utility"
|
|
146
|
-
critical_path_skills = {
|
|
147
|
-
"survey-context", "elaborate-spec", "model-domain", "define-language",
|
|
148
|
-
"design-interface", "plan-work", "plan-release", "slice-tasks",
|
|
149
|
-
"kickoff-branch", "develop-tdd", "verify-work", "audit-code",
|
|
150
|
-
"commit-message", "release-branch", "security-review",
|
|
151
|
-
"assess-impact", "investigate-bug", "validate-fix", "dispatch-agents",
|
|
152
|
-
"request-review", "respond-review", "hook-commits", "guard-git",
|
|
153
|
-
"wire-observability", "wire-ci", "smoke-test", "deploy", "enforce-first"
|
|
154
|
-
}
|
|
155
|
-
if name in critical_path_skills:
|
|
156
|
-
phase = "critical-path"
|
|
157
|
-
|
|
158
|
-
title = name.replace("-", " ").title()
|
|
159
|
-
desc_escaped = description.replace("\\", "\\\\").replace('"', '\\"')
|
|
160
|
-
|
|
161
|
-
lines = [
|
|
162
|
-
"---",
|
|
163
|
-
"okf_kind: concept",
|
|
164
|
-
"type: Skill",
|
|
165
|
-
f"id: {name}",
|
|
166
|
-
f'title: "{title}"',
|
|
167
|
-
f"name: {name}",
|
|
168
|
-
"category: skills"
|
|
169
|
-
]
|
|
170
|
-
if model:
|
|
171
|
-
lines.append(f"model: {model}")
|
|
172
|
-
lines.extend([
|
|
173
|
-
f"effort: {effort}",
|
|
174
|
-
f"phase: {phase}",
|
|
175
|
-
f'description: "{desc_escaped}"',
|
|
176
|
-
"references: []",
|
|
177
|
-
"---",
|
|
178
|
-
"",
|
|
179
|
-
f"# {title}",
|
|
180
|
-
"",
|
|
181
|
-
f"**Phase:** {phase}"
|
|
182
|
-
])
|
|
183
|
-
if model:
|
|
184
|
-
lines.append(f"**Model:** {model}")
|
|
185
|
-
lines.extend([
|
|
186
|
-
f"**Effort:** {effort}",
|
|
187
|
-
"",
|
|
188
|
-
description,
|
|
189
|
-
"",
|
|
190
|
-
f"> Auto-generated by sync-skills.sh --okf (e39s04) from skills/{name}/SKILL.md"
|
|
191
|
-
])
|
|
192
|
-
|
|
193
|
-
with open(okf_file, 'w', encoding='utf-8') as f:
|
|
194
|
-
f.write('\n'.join(lines) + '\n')
|
|
195
|
-
|
|
196
137
|
def resolve_bash():
|
|
197
138
|
"""Return an explicit path to a usable bash interpreter.
|
|
198
139
|
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""OKF concept-page rendering for srp-engine (sync-skills.sh --okf)."""
|
|
2
|
+
# story: e39s04
|
|
3
|
+
# story: BUG-2026-10-30-compliance-akita-masked
|
|
4
|
+
import os
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def render_okf_concept(skill_data, okf_wiki_skills):
|
|
8
|
+
name = skill_data['name']
|
|
9
|
+
model = skill_data['model']
|
|
10
|
+
effort = skill_data['effort']
|
|
11
|
+
description = skill_data['description']
|
|
12
|
+
|
|
13
|
+
os.makedirs(okf_wiki_skills, exist_ok=True)
|
|
14
|
+
okf_file = os.path.join(okf_wiki_skills, f"{name}.md")
|
|
15
|
+
|
|
16
|
+
phase = "utility"
|
|
17
|
+
critical_path_skills = {
|
|
18
|
+
"survey-context", "elaborate-spec", "model-domain", "define-language",
|
|
19
|
+
"design-interface", "plan-work", "plan-release", "slice-tasks",
|
|
20
|
+
"kickoff-branch", "develop-tdd", "verify-work", "audit-code",
|
|
21
|
+
"commit-message", "release-branch", "security-review",
|
|
22
|
+
"assess-impact", "investigate-bug", "validate-fix", "dispatch-agents",
|
|
23
|
+
"request-review", "respond-review", "hook-commits", "guard-git",
|
|
24
|
+
"wire-observability", "wire-ci", "smoke-test", "deploy", "enforce-first"
|
|
25
|
+
}
|
|
26
|
+
if name in critical_path_skills:
|
|
27
|
+
phase = "critical-path"
|
|
28
|
+
|
|
29
|
+
title = name.replace("-", " ").title()
|
|
30
|
+
desc_escaped = description.replace("\\", "\\\\").replace('"', '\\"')
|
|
31
|
+
|
|
32
|
+
lines = [
|
|
33
|
+
"---",
|
|
34
|
+
"okf_kind: concept",
|
|
35
|
+
"type: Skill",
|
|
36
|
+
f"id: {name}",
|
|
37
|
+
f'title: "{title}"',
|
|
38
|
+
f"name: {name}",
|
|
39
|
+
"category: skills"
|
|
40
|
+
]
|
|
41
|
+
if model:
|
|
42
|
+
lines.append(f"model: {model}")
|
|
43
|
+
lines.extend([
|
|
44
|
+
f"effort: {effort}",
|
|
45
|
+
f"phase: {phase}",
|
|
46
|
+
f'description: "{desc_escaped}"',
|
|
47
|
+
"references: []",
|
|
48
|
+
"---",
|
|
49
|
+
"",
|
|
50
|
+
f"# {title}",
|
|
51
|
+
"",
|
|
52
|
+
f"**Phase:** {phase}"
|
|
53
|
+
])
|
|
54
|
+
if model:
|
|
55
|
+
lines.append(f"**Model:** {model}")
|
|
56
|
+
lines.extend([
|
|
57
|
+
f"**Effort:** {effort}",
|
|
58
|
+
"",
|
|
59
|
+
description,
|
|
60
|
+
"",
|
|
61
|
+
f"> Auto-generated by sync-skills.sh --okf (e39s04) from skills/{name}/SKILL.md"
|
|
62
|
+
])
|
|
63
|
+
|
|
64
|
+
with open(okf_file, 'w', encoding='utf-8') as f:
|
|
65
|
+
f.write('\n'.join(lines) + '\n')
|