sillyspec 3.23.4 → 3.23.6
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/.npm-publish-userconfig.tmp +1 -0
- package/package.json +1 -1
- package/src/change-list.js +3 -1
- package/src/contract-matrix.js +12 -6
- package/src/progress.js +5 -1
- package/src/run.js +30 -8
- package/src/stage-contract.js +15 -2
|
@@ -0,0 +1 @@
|
|
|
1
|
+
//registry.npmjs.org/:_authToken=${NPM_TOKEN}
|
package/package.json
CHANGED
package/src/change-list.js
CHANGED
|
@@ -70,8 +70,10 @@ export function pathMatches(a, b) {
|
|
|
70
70
|
/**
|
|
71
71
|
* 「文件变更清单」章节标题同义词(与 src/stage-contract.js 的识别集对齐,
|
|
72
72
|
* 避免两个校验器对「有没有清单」给出矛盾结论)。
|
|
73
|
+
* 容忍可选编号前缀(`## 6. 文件变更清单` / `## 6) 文件变更清单`)——
|
|
74
|
+
* brainstorm Step11 模板鼓励 design 章节带编号,编号前缀不应让 plan-postcheck 解析失败。
|
|
73
75
|
*/
|
|
74
|
-
const FILE_LIST_SECTION_RE = /^#{2,3}\s*(文件变更清单|变更文件清单|文件清单|File Changes|Files to Change)/im
|
|
76
|
+
const FILE_LIST_SECTION_RE = /^#{2,3}\s*(?:\d+[.)]\s*)?(文件变更清单|变更文件清单|文件清单|File Changes|Files to Change)/im
|
|
75
77
|
|
|
76
78
|
/**
|
|
77
79
|
* exclude 子标题词集:「不修改/暂缓/保留」类 —— 其下路径从清单移除,不强制 task 覆盖。
|
package/src/contract-matrix.js
CHANGED
|
@@ -134,8 +134,10 @@ function parseTaskDependencies(planContent) {
|
|
|
134
134
|
* @param {string} taskName - task-04
|
|
135
135
|
* @returns {{ ok: boolean, endpoints: Array, artifactPath: string|null }}
|
|
136
136
|
*/
|
|
137
|
-
export function extractProviderArtifact(changeDir, worktreePath, specBase, taskName) {
|
|
138
|
-
|
|
137
|
+
export function extractProviderArtifact(changeDir, worktreePath, specBase, taskName, runtimeRoot) {
|
|
138
|
+
// 平台模式 contract-artifacts 落 runtimeRoot;否则落 specBase/.runtime
|
|
139
|
+
const artifactRoot = runtimeRoot || join(specBase, '.runtime')
|
|
140
|
+
const artifactDir = join(artifactRoot, 'contract-artifacts', taskName)
|
|
139
141
|
const artifactPath = join(artifactDir, 'endpoints.json')
|
|
140
142
|
|
|
141
143
|
if (!worktreePath || !existsSync(worktreePath)) {
|
|
@@ -179,13 +181,15 @@ export function extractProviderArtifact(changeDir, worktreePath, specBase, taskN
|
|
|
179
181
|
* @param {Array<{ provider: string, consumer: string, type: string }>} contracts
|
|
180
182
|
* @returns {string|null} 注入到 prompt 的契约文本,无契约时返回 null
|
|
181
183
|
*/
|
|
182
|
-
export function buildConsumerInjection(changeDir, specBase, taskName, contracts) {
|
|
184
|
+
export function buildConsumerInjection(changeDir, specBase, taskName, contracts, runtimeRoot) {
|
|
185
|
+
// 平台模式 contract-artifacts 落 runtimeRoot;否则落 specBase/.runtime
|
|
186
|
+
const artifactRoot = runtimeRoot || join(specBase, '.runtime')
|
|
183
187
|
const myContracts = contracts.filter(c => c.consumer === taskName)
|
|
184
188
|
if (myContracts.length === 0) return null
|
|
185
189
|
|
|
186
190
|
const parts = []
|
|
187
191
|
for (const contract of myContracts) {
|
|
188
|
-
const artifactDir = join(
|
|
192
|
+
const artifactDir = join(artifactRoot, 'contract-artifacts', contract.provider)
|
|
189
193
|
const artifactFile = join(artifactDir, 'endpoints.json')
|
|
190
194
|
|
|
191
195
|
let endpoints = []
|
|
@@ -293,11 +297,13 @@ export function buildContractFieldInjection(changeDir, taskName) {
|
|
|
293
297
|
* @param {string} worktreePath - worktree 路径
|
|
294
298
|
* @returns {{ ok: boolean, missingBackend: Array, unusedBackend: Array, summary: string }}
|
|
295
299
|
*/
|
|
296
|
-
export function verifyApiParity(specBase, worktreePath) {
|
|
300
|
+
export function verifyApiParity(specBase, worktreePath, runtimeRoot) {
|
|
297
301
|
const { diffApiParity } = require('./endpoint-extractor.js')
|
|
298
302
|
|
|
303
|
+
// 平台模式 contract-artifacts 落 runtimeRoot;否则落 specBase/.runtime
|
|
304
|
+
const artifactRoot = runtimeRoot || join(specBase, '.runtime')
|
|
299
305
|
// 读取所有 provider artifacts
|
|
300
|
-
const artifactBase = join(
|
|
306
|
+
const artifactBase = join(artifactRoot, 'contract-artifacts')
|
|
301
307
|
const allProviderEndpoints = []
|
|
302
308
|
|
|
303
309
|
if (existsSync(artifactBase)) {
|
package/src/progress.js
CHANGED
|
@@ -731,7 +731,11 @@ export class ProgressManager {
|
|
|
731
731
|
console.warn('⚠️ initChange: changeName 为空,跳过');
|
|
732
732
|
return null;
|
|
733
733
|
}
|
|
734
|
-
|
|
734
|
+
// quick 会话 id(quick-<uuid8>,见 run.js QUICK_SID_RE)只作 progress 的跨进程 session key,
|
|
735
|
+
// 进度存 SQL 不需要实体 change 目录——跳过避免 changes/quick-<uuid>/ 空目录残留。
|
|
736
|
+
if (!/^quick-[0-9a-f]{8}$/.test(changeName)) {
|
|
737
|
+
this._ensureChangeDir(cwd, changeName);
|
|
738
|
+
}
|
|
735
739
|
|
|
736
740
|
const db = await this._ensureDB(cwd);
|
|
737
741
|
db.transaction((sqlDb) => {
|
package/src/run.js
CHANGED
|
@@ -454,8 +454,10 @@ export async function auditQuickCompletion(cwd, guard, options = {}) {
|
|
|
454
454
|
console.log(` - ${r}`)
|
|
455
455
|
}
|
|
456
456
|
}
|
|
457
|
-
console.log(`\n
|
|
458
|
-
console.log(`
|
|
457
|
+
console.log(`\n 如确认接受这些变更,重新运行 --done 时带上对应 flag 即可解锁:`)
|
|
458
|
+
console.log(` sillyspec run quick --done --force-baseline --allow-new --change <id> --output "..."`)
|
|
459
|
+
console.log(` (--force-baseline 覆盖受保护/危险文件如 src/run.js;--allow-new 允许新增文件)`)
|
|
460
|
+
console.log(` 或在首个 sillyspec run quick 启动(step 1)时就声明这些 flag,持久化进 guard。`)
|
|
459
461
|
}
|
|
460
462
|
} catch (e) {
|
|
461
463
|
result.reasons.push(`审计失败: ${e.message}`)
|
|
@@ -1509,6 +1511,15 @@ export async function runCommand(args, cwd, specDir = null) {
|
|
|
1509
1511
|
}
|
|
1510
1512
|
}
|
|
1511
1513
|
|
|
1514
|
+
// execute 阶段必须带 --change:不允许自动检测或默认值,变更名必须由 agent 显式传入
|
|
1515
|
+
// --status 豁免(纯查看不需要指定变更)
|
|
1516
|
+
if (stageName === 'execute' && !changeName && !isStatus) {
|
|
1517
|
+
console.error('❌ execute 阶段必须用 --change <变更名> 指定要操作的变更。')
|
|
1518
|
+
console.error(' agent 必须传参,不设默认值、不做自动检测。')
|
|
1519
|
+
console.error(' 请加 --change <变更名> 重新执行。')
|
|
1520
|
+
process.exit(1)
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1512
1523
|
let progress = await pm.read(cwd, changeName)
|
|
1513
1524
|
|
|
1514
1525
|
if (!progress) {
|
|
@@ -1692,7 +1703,7 @@ export async function runCommand(args, cwd, specDir = null) {
|
|
|
1692
1703
|
// --done
|
|
1693
1704
|
if (isDone) {
|
|
1694
1705
|
const doneAnswer = getFlagValue('--answer')
|
|
1695
|
-
return await completeStep(pm, progress, stageName, cwd, outputText, inputText, { confirm: isConfirm, changeName: effectiveChange, nonInteractive: isNonInteractive && !isInteractive, platformOpts, confirmMode, doneAnswer })
|
|
1706
|
+
return await completeStep(pm, progress, stageName, cwd, outputText, inputText, { confirm: isConfirm, changeName: effectiveChange, nonInteractive: isNonInteractive && !isInteractive, platformOpts, confirmMode, doneAnswer, isForceBaseline, isAllowNew })
|
|
1696
1707
|
}
|
|
1697
1708
|
|
|
1698
1709
|
// 默认:输出当前步骤
|
|
@@ -1842,7 +1853,7 @@ async function runStage(pm, progress, stageName, cwd, changeName, skipApproval =
|
|
|
1842
1853
|
if (stageName === 'execute') {
|
|
1843
1854
|
const { generateExecuteRunId } = await import('./task-review.js')
|
|
1844
1855
|
const execSpecBase = platformOpts?.specRoot || join(cwd, '.sillyspec')
|
|
1845
|
-
const runtimeRoot = join(execSpecBase, '.runtime')
|
|
1856
|
+
const runtimeRoot = platformOpts?.runtimeRoot || join(execSpecBase, '.runtime')
|
|
1846
1857
|
const runIdFile = join(runtimeRoot, `current-execute-run-id-${changeName}`)
|
|
1847
1858
|
mkdirSync(runtimeRoot, { recursive: true })
|
|
1848
1859
|
// 优先读取已有的变更专属标记文件
|
|
@@ -1961,10 +1972,13 @@ async function runStage(pm, progress, stageName, cwd, changeName, skipApproval =
|
|
|
1961
1972
|
try {
|
|
1962
1973
|
const { execSync } = await import('child_process')
|
|
1963
1974
|
const gitStatus = execSync('git status --porcelain', { cwd, encoding: 'utf8', timeout: 10000 })
|
|
1975
|
+
// 记录全部预存脏文件(含 untracked + .sillyspec/ 路径)。quick 会话期间自身写入的元数据
|
|
1976
|
+
// (quicklog/.runtime/modules/_module-map 等)由 auditQuickCompletion 的 isQuickMetadata 精确豁免,
|
|
1977
|
+
// 不需要这里粗放过滤 .sillyspec/——旧过滤致预存 untracked .sillyspec/changes/ 不进 baseline,
|
|
1978
|
+
// 却在 audit 被当「危险(.sillyspec/)+新增」误判永久 blocked(ql-20260713-002-7628 修复)。
|
|
1964
1979
|
const baselineFiles = gitStatus
|
|
1965
1980
|
.trim().split('\n').filter(Boolean)
|
|
1966
1981
|
.map(line => line.slice(3).trim())
|
|
1967
|
-
.filter(f => !f.startsWith('.sillyspec/'))
|
|
1968
1982
|
const allowedFiles = quickOpts?.quickFiles || []
|
|
1969
1983
|
const allowNew = quickOpts?.isAllowNew || false
|
|
1970
1984
|
const forceBaseline = quickOpts?.isForceBaseline || false
|
|
@@ -2614,7 +2628,7 @@ function rollbackStageCompletion(stageData, steps, currentIdx) {
|
|
|
2614
2628
|
}
|
|
2615
2629
|
|
|
2616
2630
|
async function completeStep(pm, progress, stageName, cwd, outputText, inputText = null, options = {}) {
|
|
2617
|
-
const { printNext = true, confirm = false, changeName, platformOpts = {}, nonInteractive = false, confirmMode = null } = options
|
|
2631
|
+
const { printNext = true, confirm = false, changeName, platformOpts = {}, nonInteractive = false, confirmMode = null, isForceBaseline = false, isAllowNew = false } = options
|
|
2618
2632
|
const specBase = platformOpts.specRoot || join(cwd, '.sillyspec')
|
|
2619
2633
|
const stageData = progress.stages[stageName]
|
|
2620
2634
|
const scanProfile = stageData?.scanProfile || null
|
|
@@ -2990,7 +3004,15 @@ async function completeStep(pm, progress, stageName, cwd, outputText, inputText
|
|
|
2990
3004
|
: (existsSync(legacyGuardFile) ? JSON.parse(readFileSync(legacyGuardFile, 'utf8')) : null)
|
|
2991
3005
|
} catch {}
|
|
2992
3006
|
if (guard) {
|
|
2993
|
-
|
|
3007
|
+
// --done 的 --force-baseline/--allow-new 并入 guard(与 step1 持久化值取或)。
|
|
3008
|
+
// 修复 ql-20260713-002-7628:旧代码解析了这两个 flag 但只传 {isConfirm} 给审计,
|
|
3009
|
+
// 致 --done --force-baseline 静默无效、用户被误导「重跑 --confirm」也无法解锁。
|
|
3010
|
+
const mergedGuard = {
|
|
3011
|
+
...guard,
|
|
3012
|
+
forceBaseline: guard.forceBaseline || isForceBaseline,
|
|
3013
|
+
allowNew: guard.allowNew || isAllowNew,
|
|
3014
|
+
}
|
|
3015
|
+
const review = await auditQuickCompletion(cwd, mergedGuard, { isConfirm: confirm })
|
|
2994
3016
|
printQuickAuditReview(review)
|
|
2995
3017
|
if (review.status === 'blocked') {
|
|
2996
3018
|
steps[currentIdx].status = 'pending'
|
|
@@ -3302,7 +3324,7 @@ async function completeStep(pm, progress, stageName, cwd, outputText, inputText
|
|
|
3302
3324
|
|
|
3303
3325
|
if (planPath && existsSync(planPath)) {
|
|
3304
3326
|
const planContent = readFileSync(planPath, 'utf8')
|
|
3305
|
-
const runtimeRoot = join(effectiveSpecBase, '.runtime')
|
|
3327
|
+
const runtimeRoot = platformOpts?.runtimeRoot || join(effectiveSpecBase, '.runtime')
|
|
3306
3328
|
|
|
3307
3329
|
// execute run id:从变更专属标记文件读取
|
|
3308
3330
|
const runIdFile = join(runtimeRoot, `current-execute-run-id-${changeName}`)
|
package/src/stage-contract.js
CHANGED
|
@@ -258,8 +258,21 @@ function validateBrainstormOutputs(cwd, changeName, context = {}) {
|
|
|
258
258
|
const hasLifecycleKeyword = /\b(session|lease|agent[._-]?run|daemon|lifecycle|state[._-]?transition|claim|heartbeat)\b/i.test(content)
|
|
259
259
|
if (hasLifecycleKeyword) {
|
|
260
260
|
// 显式声明本变更不涉及生命周期契约(覆盖字段名/错误码/否定声明场景):
|
|
261
|
-
// 历史教训:design 提到 daemon_id 字段名或 daemon_not_owned 错误码就触发,被迫加空表(B3a
|
|
262
|
-
|
|
261
|
+
// 历史教训:design 提到 daemon_id 字段名或 daemon_not_owned 错误码就触发,被迫加空表(B3a)。
|
|
262
|
+
//
|
|
263
|
+
// 收紧原则(修:正常 design 不应被误判「已豁免」):
|
|
264
|
+
// - 否定词必须是明确多字短语(不涉及/不适用/未涉及/不包含/没有/n\/a/not applicable/none),
|
|
265
|
+
// 杜绝裸单字「无」与裸「na」在 40 字符宽窗口内任意命中——
|
|
266
|
+
// 「lifecycle 状态无变化」「本变更无需 lifecycle 事件」「lifecycle canal 不涉及」等不再误判;
|
|
267
|
+
// - 否定词必须与「生命周期(契约)/lifecycle(contract)」紧邻(仅允许少量空白/分隔符/「任何」),
|
|
268
|
+
// 不再用 40 字符宽松窗口;工具错误信息本身就指引写「不涉及生命周期契约」这个规范短语。
|
|
269
|
+
const declaresNotApplicable =
|
|
270
|
+
// 否定在前:「不涉及生命周期(契约)」「不适用 lifecycle contract」
|
|
271
|
+
/(?:不涉及|不适用|未涉及|不包含|没有(?:任何)?)\s?(?:任何\s?)?(?:生命周期(?:契约)?|lifecycle(?:[ _=-]?contract)?)/i.test(content) ||
|
|
272
|
+
// 主题在前(表格/列表单元「生命周期契约:不涉及 / N/A / 无」——分隔符强制,杜绝宽窗口)
|
|
273
|
+
/(?:生命周期(?:契约)?|lifecycle(?:[ _=-]?contract)?)\s?[::=]\s?(?:不涉及|不适用|未涉及|无|n\/?a\b|not[ _=-]?applicable|none\b)/i.test(content) ||
|
|
274
|
+
// 英文谓语句:「does not involve / not applicable ... lifecycle」
|
|
275
|
+
/(?:does[ _-]?not[ _-]?involve|not[ _-]?applicable)[^\n]{0,15}lifecycle/i.test(content)
|
|
263
276
|
if (declaresNotApplicable) {
|
|
264
277
|
warnings.push('design.md 显式声明不涉及生命周期契约 — 已豁免「生命周期契约表」要求')
|
|
265
278
|
} else {
|