@gobing-ai/knowledge-kit 0.0.12 → 0.0.14
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/.claude-plugin/marketplace.json +1 -1
- package/dist/index.js +120 -26
- package/package.json +1 -1
- package/plugins/generations/content-gen/dist/index.js +22187 -0
- package/plugins/generations/content-gen/plugin.json +1 -1
- package/plugins/generations/core-facts-gen/dist/index.js +22068 -0
- package/plugins/generations/core-facts-gen/plugin.json +1 -1
- package/plugins/generations/daily-article-gen/dist/index.js +22050 -0
- package/plugins/generations/daily-article-gen/plugin.json +1 -1
- package/plugins/generations/daily-article-gen/src/index.ts +13 -1
- package/plugins/generations/dailynews-gen/dist/index.js +22344 -0
- package/plugins/generations/dailynews-gen/plugin.json +1 -1
- package/plugins/generations/episode-plan-gen/dist/index.js +22503 -0
- package/plugins/generations/episode-plan-gen/plugin.json +1 -1
- package/plugins/generations/episode-plan-gen/src/index.ts +11 -0
- package/plugins/generations/image-gen/config.example.yaml +75 -0
- package/plugins/generations/image-gen/dist/index.js +24862 -0
- package/plugins/generations/image-gen/package.json +17 -0
- package/plugins/generations/image-gen/plugin.json +7 -0
- package/plugins/generations/image-gen/presets/formats/cover.yaml +57 -0
- package/plugins/generations/image-gen/presets/formats/free.yaml +46 -0
- package/plugins/generations/image-gen/presets/formats/illustration.yaml +48 -0
- package/plugins/generations/image-gen/presets/styles/clean-webapp-ui.yaml +28 -0
- package/plugins/generations/image-gen/presets/styles/cute.yaml +3 -0
- package/plugins/generations/image-gen/presets/styles/editorial.yaml +3 -0
- package/plugins/generations/image-gen/presets/styles/fresh.yaml +3 -0
- package/plugins/generations/image-gen/presets/styles/minimalist.yaml +3 -0
- package/plugins/generations/image-gen/presets/styles/photorealistic.yaml +3 -0
- package/plugins/generations/image-gen/presets/styles/sketch.yaml +3 -0
- package/plugins/generations/image-gen/presets/styles/technical-diagram.yaml +3 -0
- package/plugins/generations/image-gen/presets/styles/vibrant.yaml +3 -0
- package/plugins/generations/image-gen/presets/styles/warm.yaml +3 -0
- package/plugins/generations/image-gen/src/bytes.ts +19 -0
- package/plugins/generations/image-gen/src/index.ts +319 -0
- package/plugins/generations/image-gen/src/job.ts +143 -0
- package/plugins/generations/image-gen/src/paths.ts +31 -0
- package/plugins/generations/image-gen/src/presets.ts +344 -0
- package/plugins/generations/image-gen/src/providers/agnes.ts +110 -0
- package/plugins/generations/image-gen/src/providers/azure.ts +153 -0
- package/plugins/generations/image-gen/src/providers/codex-cli.ts +170 -0
- package/plugins/generations/image-gen/src/providers/dashscope.ts +485 -0
- package/plugins/generations/image-gen/src/providers/google.ts +268 -0
- package/plugins/generations/image-gen/src/providers/huggingface.ts +59 -0
- package/plugins/generations/image-gen/src/providers/jimeng.ts +259 -0
- package/plugins/generations/image-gen/src/providers/minimax.ts +171 -0
- package/plugins/generations/image-gen/src/providers/openai.ts +319 -0
- package/plugins/generations/image-gen/src/providers/openrouter.ts +257 -0
- package/plugins/generations/image-gen/src/providers/refs.ts +24 -0
- package/plugins/generations/image-gen/src/providers/replicate.ts +279 -0
- package/plugins/generations/image-gen/src/providers/seedream.ts +128 -0
- package/plugins/generations/image-gen/src/providers/types.ts +286 -0
- package/plugins/generations/image-gen/src/providers/zai.ts +237 -0
- package/plugins/generations/image-gen/tsconfig.json +8 -0
- package/plugins/generations/news-report-gen/dist/index.js +22193 -0
- package/plugins/generations/news-report-gen/package.json +17 -0
- package/plugins/generations/news-report-gen/plugin.json +7 -0
- package/plugins/generations/news-report-gen/src/index.ts +308 -0
- package/plugins/generations/news-report-gen/tsconfig.json +4 -0
- package/plugins/generations/omni-voice-gen/Makefile +14 -0
- package/plugins/generations/omni-voice-gen/README.md +112 -0
- package/plugins/generations/omni-voice-gen/bin/omni-voice-gen +2 -0
- package/plugins/generations/omni-voice-gen/dist/omni-voice-gen-prr8skpb. +2 -0
- package/plugins/generations/omni-voice-gen/dist/omni-voice-gen.js +6 -0
- package/plugins/generations/omni-voice-gen/plugin.json +6 -0
- package/plugins/generations/omni-voice-gen/profiles.json +12 -0
- package/plugins/generations/omni-voice-gen/pyproject.toml +25 -0
- package/plugins/generations/omni-voice-gen/scripts/coverage_gate.py +74 -0
- package/plugins/generations/omni-voice-gen/src/omni_voice_gen/__init__.py +1 -0
- package/plugins/generations/omni-voice-gen/src/omni_voice_gen/__main__.py +39 -0
- package/plugins/generations/omni-voice-gen/src/omni_voice_gen/audio.py +190 -0
- package/plugins/generations/omni-voice-gen/src/omni_voice_gen/backend.py +150 -0
- package/plugins/generations/omni-voice-gen/src/omni_voice_gen/contract.py +76 -0
- package/plugins/generations/omni-voice-gen/src/omni_voice_gen/mp3.py +60 -0
- package/plugins/generations/omni-voice-gen/src/omni_voice_gen/pipeline.py +289 -0
- package/plugins/generations/omni-voice-gen/src/omni_voice_gen/profiles.py +100 -0
- package/plugins/generations/omni-voice-gen/src/omni_voice_gen/qc.py +234 -0
- package/plugins/generations/omni-voice-gen/src/omni_voice_gen/voicescript.py +352 -0
- package/plugins/generations/omni-voice-gen/uv.lock +3510 -0
- package/plugins/generations/voice-gen/dist/index.js +23055 -0
- package/plugins/generations/voice-gen/plugin.json +1 -1
- package/plugins/generations/voice-gen/src/index.ts +16 -1
- package/plugins/generations/voice-gen/src/voicebox-client.ts +3 -1
- package/plugins/ingestions/aihot-ingest/dist/index.js +22378 -0
- package/plugins/ingestions/aihot-ingest/plugin.json +1 -1
- package/plugins/ingestions/horizon-ingest/dist/index.js +22125 -0
- package/plugins/ingestions/horizon-ingest/plugin.json +1 -1
- package/plugins/ingestions/karakeep-local/dist/index.js +24204 -0
- package/plugins/ingestions/karakeep-local/plugin.json +1 -1
- package/plugins/ingestions/last30days-ingest/dist/index.js +22070 -0
- package/plugins/ingestions/last30days-ingest/plugin.json +1 -1
- package/plugins/ingestions/web-search/dist/index.js +24399 -0
- package/plugins/ingestions/web-search/plugin.json +1 -1
- package/plugins/kk/commands/image-extract.md +40 -0
- package/plugins/kk/commands/image-generate.md +32 -0
- package/plugins/kk/config.example.yaml +80 -0
- package/plugins/kk/plugin.json +1 -1
- package/plugins/kk/skills/image-authoring/SKILL.md +257 -0
- package/plugins/kk/skills/image-authoring/references/format-drafting.md +57 -0
- package/plugins/kk/skills/image-authoring/references/illustration-positions.md +87 -0
- package/plugins/kk/skills/image-authoring/references/migrating-from-wt.md +31 -0
- package/plugins/kk/skills/image-authoring/references/providers.md +52 -0
- package/plugins/kk/skills/image-authoring/references/style-extraction.md +139 -0
- package/plugins/kk/workflows/kk-daily-ai-voice.yaml +130 -30
- package/plugins/publishings/emdash-pub/dist/index.js +22263 -0
- package/plugins/publishings/emdash-pub/plugin.json +1 -1
- package/plugins/publishings/podcast-pub/dist/index.js +22650 -0
- package/plugins/publishings/podcast-pub/plugin.json +8 -2
- package/plugins/publishings/podcast-pub/src/index.ts +18 -2
- package/plugins/publishings/podcast-pub/src/show-notes.ts +56 -9
- package/plugins/publishings/qiita-pub/dist/index.js +22101 -0
- package/plugins/publishings/qiita-pub/plugin.json +1 -1
- package/plugins/publishings/surfdash-pub/dist/index.js +22323 -0
- package/plugins/publishings/surfdash-pub/plugin.json +1 -1
- package/plugins/publishings/surfdash-pub/src/index.ts +109 -9
- package/plugins/publishings/zenn-pub/dist/index.js +22142 -0
- package/plugins/publishings/zenn-pub/plugin.json +1 -1
- package/plugins/sp/scripts/batch-preflight.mjs +346 -0
- package/plugins/sp/scripts/batch-preflight.ts +459 -0
- package/plugins/sp/scripts/daily-summary/daily-summary.mjs +615 -0
- package/plugins/sp/scripts/daily-summary/daily-summary.ts +846 -0
- package/plugins/sp/scripts/daily-summary/logger.ts +28 -0
- package/plugins/sp/scripts/dogfood-testing/detect-pipeline-driving.mjs +223 -0
- package/plugins/sp/scripts/dogfood-testing/detect-pipeline-driving.ts +367 -0
- package/plugins/sp/scripts/dogfood-testing/validate-report.mjs +132 -0
- package/plugins/sp/scripts/dogfood-testing/validate-report.ts +169 -0
- package/plugins/sp/scripts/feature-dev-precheck.mjs +171 -0
- package/plugins/sp/scripts/feature-dev-precheck.ts +238 -0
- package/plugins/sp/scripts/feature-sync-bounded.mjs +285 -0
- package/plugins/sp/scripts/feature-sync-bounded.ts +478 -0
- package/plugins/sp/scripts/history-anatomy-cache.mjs +902 -0
- package/plugins/sp/scripts/history-anatomy-cache.ts +1028 -0
- package/plugins/sp/scripts/idea-handoff.mjs +22 -0
- package/plugins/sp/scripts/idea-handoff.ts +44 -0
- package/plugins/sp/scripts/inline-pipeline-parity-check.ts +185 -0
- package/plugins/sp/scripts/inline-run-setup.ts +198 -0
- package/plugins/sp/scripts/pr-reviewing.mjs +769 -0
- package/plugins/sp/scripts/pr-reviewing.ts +925 -0
- package/plugins/sp/scripts/quality-gate.mjs +179 -0
- package/plugins/sp/scripts/quality-gate.ts +217 -0
- package/plugins/sp/scripts/script-contract-check.ts +319 -0
- package/plugins/sp/scripts/stage-registry-adapter.ts +1533 -0
- package/plugins/sp/scripts/surface-drift-inventory.ts +929 -0
- package/plugins/sp/scripts/task-evidence-precheck.ts +181 -0
- package/plugins/sp/scripts/task-size-precheck.ts +175 -0
- package/plugins/sp/scripts/transition-shim-check.ts +238 -0
- package/plugins/sp/scripts/validate-commands.ts +689 -0
- package/plugins/sp/scripts/validate-flag-contracts.ts +878 -0
- package/plugins/sp/scripts/verify-answer-lint.ts +530 -0
- package/plugins/sp/scripts/workflow-step-profile.mjs +316 -0
- package/plugins/sp/scripts/workflow-step-profile.ts +456 -0
- package/plugins/sp/scripts/wrapup-steps.mjs +373 -0
- package/plugins/sp/scripts/wrapup-steps.ts +466 -0
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// @bun
|
|
3
|
+
|
|
4
|
+
// plugins/sp/scripts/wrapup-steps.ts
|
|
5
|
+
import { spawnSync } from 'node:child_process';
|
|
6
|
+
import { appendFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
7
|
+
import { join } from 'node:path';
|
|
8
|
+
|
|
9
|
+
function jqPick(...values) {
|
|
10
|
+
for (const value of values) {
|
|
11
|
+
if (value !== null && value !== undefined && value !== false) return value;
|
|
12
|
+
}
|
|
13
|
+
return values[values.length - 1];
|
|
14
|
+
}
|
|
15
|
+
function jqText(value) {
|
|
16
|
+
return typeof value === 'string' ? value : JSON.stringify(value);
|
|
17
|
+
}
|
|
18
|
+
var WBS_PATTERN = /^[0-9]{4}$/;
|
|
19
|
+
function spurCommand(spurBin) {
|
|
20
|
+
const parts = (spurBin ?? 'spur')
|
|
21
|
+
.trim()
|
|
22
|
+
.split(/\s+/)
|
|
23
|
+
.filter((p) => p.length > 0);
|
|
24
|
+
return { cmd: parts[0] ?? 'spur', prefix: parts.slice(1) };
|
|
25
|
+
}
|
|
26
|
+
function spur(env, args, options = {}) {
|
|
27
|
+
const { cmd, prefix } = spurCommand(env.spurBin);
|
|
28
|
+
const result = spawnSync(cmd, [...prefix, ...args], {
|
|
29
|
+
cwd: options.cwd,
|
|
30
|
+
encoding: 'utf8',
|
|
31
|
+
...(options.stderr === 'inherit' ? { stdio: ['ignore', 'pipe', 'inherit'] } : {}),
|
|
32
|
+
});
|
|
33
|
+
if (result.error !== undefined) return { status: result.status ?? 1, stdout: '' };
|
|
34
|
+
return { status: result.status ?? 1, stdout: result.stdout ?? '' };
|
|
35
|
+
}
|
|
36
|
+
function taskStatusOf(taskJson) {
|
|
37
|
+
let parsed;
|
|
38
|
+
try {
|
|
39
|
+
parsed = JSON.parse(taskJson);
|
|
40
|
+
} catch {
|
|
41
|
+
return { resolved: null, present: false };
|
|
42
|
+
}
|
|
43
|
+
if (parsed === null || typeof parsed !== 'object') return { resolved: null, present: false };
|
|
44
|
+
const frontmatter = parsed.frontmatter;
|
|
45
|
+
const fmStatus = frontmatter !== null && typeof frontmatter === 'object' ? frontmatter.status : undefined;
|
|
46
|
+
const status = parsed.status;
|
|
47
|
+
if (fmStatus === null || fmStatus === undefined) {
|
|
48
|
+
return { resolved: status ?? null, present: status !== undefined && status !== null };
|
|
49
|
+
}
|
|
50
|
+
return { resolved: fmStatus, present: true };
|
|
51
|
+
}
|
|
52
|
+
function statusDisplay(taskJson) {
|
|
53
|
+
const { resolved } = taskStatusOf(taskJson);
|
|
54
|
+
if (resolved === null || resolved === undefined || resolved === false) return 'unresolved';
|
|
55
|
+
return jqText(resolved);
|
|
56
|
+
}
|
|
57
|
+
function resolveTasks(env, options = {}) {
|
|
58
|
+
const cwd = options.cwd;
|
|
59
|
+
const runId = env.__runId ?? '';
|
|
60
|
+
if (runId.length === 0) {
|
|
61
|
+
process.stderr.write(`task-resolve: __runId is empty \u2014 refusing the legacy fixed-path fallback
|
|
62
|
+
`);
|
|
63
|
+
return { status: 'FAIL', statusFile: '', tasksFile: '', exitCode: 1 };
|
|
64
|
+
}
|
|
65
|
+
mkdirSync(cwd ? join(cwd, '.spur', 'run') : join('.spur', 'run'), { recursive: true });
|
|
66
|
+
const relTasksFile = join('.spur', 'run', `${runId}-wrapup-tasks.json`);
|
|
67
|
+
const relReasonFile = join('.spur', 'run', `${runId}-route-reason.txt`);
|
|
68
|
+
const relStatusFile = join('.spur', 'run', `${runId}-wrapup-resolve.status`);
|
|
69
|
+
const abs = (p) => (cwd ? join(cwd, p) : p);
|
|
70
|
+
const writeFail = (reason) => {
|
|
71
|
+
writeFileSync(abs(relReasonFile), reason);
|
|
72
|
+
writeFileSync(
|
|
73
|
+
abs(relStatusFile),
|
|
74
|
+
`FAIL
|
|
75
|
+
`,
|
|
76
|
+
);
|
|
77
|
+
return { status: 'FAIL', statusFile: relStatusFile, tasksFile: relTasksFile, exitCode: 0 };
|
|
78
|
+
};
|
|
79
|
+
let parsedTasks;
|
|
80
|
+
try {
|
|
81
|
+
parsedTasks = JSON.parse(env.tasks ?? '');
|
|
82
|
+
} catch {
|
|
83
|
+
parsedTasks = undefined;
|
|
84
|
+
}
|
|
85
|
+
const validArray =
|
|
86
|
+
Array.isArray(parsedTasks) && parsedTasks.every((w) => typeof w === 'string' && WBS_PATTERN.test(w));
|
|
87
|
+
if (!validArray) {
|
|
88
|
+
process.stderr.write(`task-resolve: tasks must be a JSON array of canonical four-digit WBS strings (whitespace is rejected, not trimmed)
|
|
89
|
+
`);
|
|
90
|
+
return writeFail('failed:tasks is not a JSON array of canonical four-digit WBS strings');
|
|
91
|
+
}
|
|
92
|
+
const deduped = [];
|
|
93
|
+
for (const wbs of parsedTasks) {
|
|
94
|
+
if (!deduped.includes(wbs)) deduped.push(wbs);
|
|
95
|
+
}
|
|
96
|
+
writeFileSync(
|
|
97
|
+
abs(relTasksFile),
|
|
98
|
+
`${JSON.stringify(deduped)}
|
|
99
|
+
`,
|
|
100
|
+
);
|
|
101
|
+
let unresolved = false;
|
|
102
|
+
for (const wbs of deduped) {
|
|
103
|
+
const shown = spur(env, ['task', 'show', wbs, '--json'], { cwd });
|
|
104
|
+
const status = shown.status === 0 ? statusDisplay(shown.stdout) : 'unresolved';
|
|
105
|
+
if (status !== 'done' && status !== 'cancelled') {
|
|
106
|
+
process.stderr.write(`task-resolve: task ${wbs} did not resolve to a completed status (status=${status})
|
|
107
|
+
`);
|
|
108
|
+
unresolved = true;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (unresolved) {
|
|
112
|
+
return writeFail(`failed:unresolved or non-completed task (see ${relTasksFile})`);
|
|
113
|
+
}
|
|
114
|
+
writeFileSync(
|
|
115
|
+
abs(relStatusFile),
|
|
116
|
+
`PASS
|
|
117
|
+
`,
|
|
118
|
+
);
|
|
119
|
+
return { status: 'PASS', statusFile: relStatusFile, tasksFile: relTasksFile, exitCode: 0 };
|
|
120
|
+
}
|
|
121
|
+
function runMetrics(env, options = {}) {
|
|
122
|
+
const cwd = options.cwd;
|
|
123
|
+
const runId = env.__runId ?? '';
|
|
124
|
+
mkdirSync(cwd ? join(cwd, '.spur', 'run') : join('.spur', 'run'), { recursive: true });
|
|
125
|
+
mkdirSync(cwd ? join(cwd, '.spur', 'memory') : join('.spur', 'memory'), { recursive: true });
|
|
126
|
+
const relStatusFile = join('.spur', 'run', `${runId}-wrapup-metrics.status`);
|
|
127
|
+
const relTasksFile = join('.spur', 'run', `${runId}-wrapup-tasks.json`);
|
|
128
|
+
const relMetricsFile = join('.spur', 'memory', 'wrapup-metrics.jsonl');
|
|
129
|
+
const abs = (p) => (cwd ? join(cwd, p) : p);
|
|
130
|
+
const fail = () => {
|
|
131
|
+
writeFileSync(
|
|
132
|
+
abs(relStatusFile),
|
|
133
|
+
`FAIL
|
|
134
|
+
`,
|
|
135
|
+
);
|
|
136
|
+
return { status: 'FAIL', statusFile: relStatusFile };
|
|
137
|
+
};
|
|
138
|
+
let captured;
|
|
139
|
+
try {
|
|
140
|
+
captured = JSON.parse(readFileSync(abs(relTasksFile), 'utf8'));
|
|
141
|
+
} catch {
|
|
142
|
+
captured = undefined;
|
|
143
|
+
}
|
|
144
|
+
const validCapture = Array.isArray(captured) && captured.every((w) => typeof w === 'string' && WBS_PATTERN.test(w));
|
|
145
|
+
if (!validCapture) {
|
|
146
|
+
process.stderr.write(`metrics-record: run-scoped task capture missing, corrupted or non-canonical \u2014 refusing to record metrics
|
|
147
|
+
`);
|
|
148
|
+
return fail();
|
|
149
|
+
}
|
|
150
|
+
let metricsRc = 0;
|
|
151
|
+
for (const wbs of captured) {
|
|
152
|
+
const shown = spur(env, ['task', 'show', wbs, '--json'], { cwd });
|
|
153
|
+
const lookup = shown.status === 0 ? taskStatusOf(shown.stdout) : { resolved: null, present: false };
|
|
154
|
+
if (!lookup.present) {
|
|
155
|
+
process.stderr.write(`metrics-record: task ${wbs} lookup failed or was malformed \u2014 recording FAIL instead of silently omitting its metrics row
|
|
156
|
+
`);
|
|
157
|
+
metricsRc = 1;
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
const parsed = JSON.parse(shown.stdout);
|
|
161
|
+
const frontmatter =
|
|
162
|
+
parsed.frontmatter !== null && typeof parsed.frontmatter === 'object' ? parsed.frontmatter : {};
|
|
163
|
+
const featureId = String(jqPick(frontmatter.feature_id, parsed.feature_id, ''));
|
|
164
|
+
const status2 = String(jqPick(frontmatter.status, parsed.status, 'unknown'));
|
|
165
|
+
let verdict = 'UNKNOWN';
|
|
166
|
+
const verdictPath = join('.spur', 'run', `${wbs}-verdict.json`);
|
|
167
|
+
if (existsSync(abs(verdictPath))) {
|
|
168
|
+
try {
|
|
169
|
+
const raw = jqPick(JSON.parse(readFileSync(abs(verdictPath), 'utf8')).verdict, 'UNKNOWN');
|
|
170
|
+
const text = raw === 'UNKNOWN' ? 'UNKNOWN' : jqText(raw);
|
|
171
|
+
if (text.length > 0) verdict = text;
|
|
172
|
+
} catch {}
|
|
173
|
+
}
|
|
174
|
+
const timestamp = new Date().toISOString().replace(/\.\d{3}Z$/, 'Z');
|
|
175
|
+
const row = { wbs, feature_id: featureId, status: status2, verdict, timestamp };
|
|
176
|
+
try {
|
|
177
|
+
appendFileSync(
|
|
178
|
+
abs(relMetricsFile),
|
|
179
|
+
`${JSON.stringify(row)}
|
|
180
|
+
`,
|
|
181
|
+
);
|
|
182
|
+
} catch {
|
|
183
|
+
process.stderr.write(`metrics-record: metrics append failed for task ${wbs} \u2014 recording FAIL instead of claiming the row landed
|
|
184
|
+
`);
|
|
185
|
+
metricsRc = 1;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
const status = metricsRc === 0 ? 'PASS' : 'FAIL';
|
|
189
|
+
writeFileSync(
|
|
190
|
+
abs(relStatusFile),
|
|
191
|
+
`${status}
|
|
192
|
+
`,
|
|
193
|
+
);
|
|
194
|
+
return { status, statusFile: relStatusFile };
|
|
195
|
+
}
|
|
196
|
+
function classifySync(syncOutput, syncRc, feature, observed) {
|
|
197
|
+
if (syncRc !== 0) {
|
|
198
|
+
return { reason: `sync exited nonzero (rc=${syncRc})`, applied: 'unreadable', syncOk: false };
|
|
199
|
+
}
|
|
200
|
+
let parsed;
|
|
201
|
+
try {
|
|
202
|
+
parsed = JSON.parse(syncOutput);
|
|
203
|
+
} catch {
|
|
204
|
+
parsed = undefined;
|
|
205
|
+
}
|
|
206
|
+
const obj = parsed !== null && typeof parsed === 'object' ? parsed : undefined;
|
|
207
|
+
const proposal = obj?.proposal !== null && typeof obj?.proposal === 'object' ? obj.proposal : undefined;
|
|
208
|
+
const shapeOk =
|
|
209
|
+
obj !== undefined &&
|
|
210
|
+
proposal !== undefined &&
|
|
211
|
+
typeof proposal.featureId === 'string' &&
|
|
212
|
+
typeof proposal.from === 'string' &&
|
|
213
|
+
typeof proposal.to === 'string' &&
|
|
214
|
+
typeof obj.applied === 'boolean';
|
|
215
|
+
if (!shapeOk) {
|
|
216
|
+
return { reason: 'malformed or unreadable sync result', applied: 'unreadable', syncOk: false };
|
|
217
|
+
}
|
|
218
|
+
const applied = obj.applied === true ? 'true' : 'false';
|
|
219
|
+
const pFrom = String(jqPick(proposal.from, ''));
|
|
220
|
+
const pTo = String(jqPick(proposal.to, ''));
|
|
221
|
+
if (String(jqPick(proposal.featureId, '')) !== feature) {
|
|
222
|
+
return { reason: `sync proposal does not match feature ${feature}`, applied, syncOk: false };
|
|
223
|
+
}
|
|
224
|
+
if (proposal.gateBlocked === true) {
|
|
225
|
+
return {
|
|
226
|
+
reason: 'sync proposal is gate-blocked \u2014 a blocked sync is not a no-change success',
|
|
227
|
+
applied,
|
|
228
|
+
syncOk: false,
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
if (proposal.requiresConfirm === true) {
|
|
232
|
+
return { reason: 'sync proposal requires operator confirmation', applied, syncOk: false };
|
|
233
|
+
}
|
|
234
|
+
if (applied === 'true' && observed !== pTo) {
|
|
235
|
+
return {
|
|
236
|
+
reason: `applied sync did not land on the proposal target (observed=${observed}, to=${pTo})`,
|
|
237
|
+
applied,
|
|
238
|
+
syncOk: false,
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
if (applied === 'false' && (pFrom !== pTo || observed !== pTo)) {
|
|
242
|
+
return {
|
|
243
|
+
reason: `sync applied nothing without a from==to observed no-op (from=${pFrom}, to=${pTo}, observed=${observed})`,
|
|
244
|
+
applied,
|
|
245
|
+
syncOk: false,
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
return { reason: '', applied, syncOk: true };
|
|
249
|
+
}
|
|
250
|
+
function runFeatureTransition(env, options = {}) {
|
|
251
|
+
const cwd = options.cwd;
|
|
252
|
+
const runId = env.__runId ?? '';
|
|
253
|
+
const feature = env.feature ?? '';
|
|
254
|
+
mkdirSync(cwd ? join(cwd, '.spur', 'run') : join('.spur', 'run'), { recursive: true });
|
|
255
|
+
const relStatusFile = join('.spur', 'run', `${runId}-wrapup-sync.status`);
|
|
256
|
+
const abs = (p) => (cwd ? join(cwd, p) : p);
|
|
257
|
+
if (feature.length === 0) {
|
|
258
|
+
process.stderr.write(`feature-transition: vars.feature is empty \u2014 refusing no-op feature sync (mis-invocation, not a blocked sync)
|
|
259
|
+
`);
|
|
260
|
+
return { status: 'FAIL', statusFile: relStatusFile, exitCode: 1 };
|
|
261
|
+
}
|
|
262
|
+
let syncOutput = '';
|
|
263
|
+
let syncRc = 1;
|
|
264
|
+
const boundedTs = join('plugins', 'sp', 'scripts', 'feature-sync-bounded.ts');
|
|
265
|
+
const boundedArgs = [feature, '--spur-bin', env.spurBin ?? 'spur', '--json'];
|
|
266
|
+
if (existsSync(cwd ? join(cwd, boundedTs) : boundedTs)) {
|
|
267
|
+
const result = spawnSync('bun', [boundedTs, ...boundedArgs], { cwd, encoding: 'utf8' });
|
|
268
|
+
syncOutput = result.stdout ?? '';
|
|
269
|
+
syncRc = result.status ?? 1;
|
|
270
|
+
if (result.stderr !== null && result.stderr.length > 0) process.stderr.write(result.stderr);
|
|
271
|
+
} else {
|
|
272
|
+
const probe = spawnSync('superskill', ['script', 'path', 'sp', 'feature-sync-bounded.mjs'], {
|
|
273
|
+
cwd,
|
|
274
|
+
encoding: 'utf8',
|
|
275
|
+
});
|
|
276
|
+
const twin = probe.status === 0 ? (probe.stdout ?? '').trim() : '';
|
|
277
|
+
if (twin.length > 0 && existsSync(twin)) {
|
|
278
|
+
const result = spawnSync('node', [twin, ...boundedArgs], { cwd, encoding: 'utf8' });
|
|
279
|
+
syncOutput = result.stdout ?? '';
|
|
280
|
+
syncRc = result.status ?? 1;
|
|
281
|
+
if (result.stderr !== null && result.stderr.length > 0) process.stderr.write(result.stderr);
|
|
282
|
+
} else {
|
|
283
|
+
const result = spur(env, ['feature', 'sync', feature, '--json'], { cwd, stderr: 'inherit' });
|
|
284
|
+
syncOutput = result.stdout;
|
|
285
|
+
syncRc = result.status;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
process.stdout.write(`${syncOutput}
|
|
289
|
+
`);
|
|
290
|
+
const shown = spur(env, ['feature', 'show', feature, '--json'], { cwd });
|
|
291
|
+
let observed = '';
|
|
292
|
+
if (shown.status === 0) {
|
|
293
|
+
try {
|
|
294
|
+
const parsed = JSON.parse(shown.stdout);
|
|
295
|
+
const frontmatter =
|
|
296
|
+
parsed.frontmatter !== null && typeof parsed.frontmatter === 'object' ? parsed.frontmatter : {};
|
|
297
|
+
const picked = jqPick(parsed.status, frontmatter.status, '');
|
|
298
|
+
observed = picked === '' ? '' : jqText(picked);
|
|
299
|
+
} catch {
|
|
300
|
+
observed = '';
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
if (observed.length === 0) observed = 'unreadable';
|
|
304
|
+
const classified = classifySync(syncOutput, syncRc, feature, observed);
|
|
305
|
+
const { reason, applied } = classified;
|
|
306
|
+
const syncOk = classified.syncOk;
|
|
307
|
+
let gate = 'skipped';
|
|
308
|
+
if (applied === 'true' || syncRc !== 0) {
|
|
309
|
+
process.stdout.write(`feature-transition: sync applied or failed after a possible partial transition for ${feature} \u2014 running feature gate: ${env.featureGateCmd ?? ''}
|
|
310
|
+
`);
|
|
311
|
+
const gateResult = spawnSync('sh', ['-c', env.featureGateCmd ?? ''], { cwd, stdio: 'inherit' });
|
|
312
|
+
if ((gateResult.status ?? 1) === 0) {
|
|
313
|
+
gate = 'PASS';
|
|
314
|
+
process.stdout.write(`feature-transition: feature gate PASS for feature ${feature}
|
|
315
|
+
`);
|
|
316
|
+
} else {
|
|
317
|
+
gate = 'FAIL';
|
|
318
|
+
process.stderr.write(`feature-transition: feature gate FAIL for feature ${feature} \u2014 inspect findings before reporting the transition complete
|
|
319
|
+
`);
|
|
320
|
+
}
|
|
321
|
+
} else {
|
|
322
|
+
process.stdout.write(`feature-transition: sync did not apply a transition (rc=${syncRc}, applied=${applied}) \u2014 feature gate skipped
|
|
323
|
+
`);
|
|
324
|
+
}
|
|
325
|
+
let syncStatus;
|
|
326
|
+
if (!syncOk || gate === 'FAIL') {
|
|
327
|
+
syncStatus = 'FAIL';
|
|
328
|
+
process.stderr.write(`feature-transition: required synchronization failed for ${feature} \u2014 ${reason}; gate=${gate}
|
|
329
|
+
`);
|
|
330
|
+
} else if (applied === 'false') {
|
|
331
|
+
syncStatus = 'PASS';
|
|
332
|
+
process.stdout.write(`feature-transition: feature sync verified for ${feature} (from==to observed at ${observed}, gate=${gate}) \u2014 explicit no-change
|
|
333
|
+
`);
|
|
334
|
+
} else {
|
|
335
|
+
syncStatus = 'PASS';
|
|
336
|
+
process.stdout.write(`feature-transition: feature sync verified for ${feature} (applied, observed=${observed}, gate=${gate})
|
|
337
|
+
`);
|
|
338
|
+
}
|
|
339
|
+
writeFileSync(
|
|
340
|
+
abs(relStatusFile),
|
|
341
|
+
`${syncStatus}
|
|
342
|
+
`,
|
|
343
|
+
);
|
|
344
|
+
return { status: syncStatus, statusFile: relStatusFile, exitCode: 0 };
|
|
345
|
+
}
|
|
346
|
+
var WRAPUP_STEPS_USAGE =
|
|
347
|
+
'usage: wrapup-steps.ts <resolve|metrics|feature-transition> (env: __runId, tasks, feature, featureGateCmd, spurBin)';
|
|
348
|
+
function main(argv, env = process.env, options = {}) {
|
|
349
|
+
const sub = argv[0];
|
|
350
|
+
if (sub === 'resolve') return resolveTasks(env, options).exitCode;
|
|
351
|
+
if (sub === 'metrics') {
|
|
352
|
+
runMetrics(env, options);
|
|
353
|
+
return 0;
|
|
354
|
+
}
|
|
355
|
+
if (sub === 'feature-transition') return runFeatureTransition(env, options).exitCode;
|
|
356
|
+
process.stderr.write(`${WRAPUP_STEPS_USAGE}
|
|
357
|
+
`);
|
|
358
|
+
return 2;
|
|
359
|
+
}
|
|
360
|
+
process.exit(main(process.argv.slice(2)));
|
|
361
|
+
|
|
362
|
+
export {
|
|
363
|
+
classifySync,
|
|
364
|
+
jqPick,
|
|
365
|
+
main,
|
|
366
|
+
resolveTasks,
|
|
367
|
+
runFeatureTransition,
|
|
368
|
+
runMetrics,
|
|
369
|
+
spurCommand,
|
|
370
|
+
taskStatusOf,
|
|
371
|
+
WBS_PATTERN,
|
|
372
|
+
WRAPUP_STEPS_USAGE,
|
|
373
|
+
};
|