@dzhechkov/skills-feature-adr 1.3.43 → 1.3.44
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 +9 -0
- package/package.json +1 -1
- package/templates/.claude/workflows/feature-adr.js +123 -10
package/README.md
CHANGED
|
@@ -124,6 +124,15 @@ writing code. Without this the run stalls; with `coder: 'codex-fallback'` the pi
|
|
|
124
124
|
unavailable (limit?) — falling back to Codex auto"* and finishes the code + tests on Codex, no
|
|
125
125
|
restart and no lost work.
|
|
126
126
|
|
|
127
|
+
**Codex writes are out-of-band — the Step-7.5 landed barrier waits for them.** Codex applies edits via
|
|
128
|
+
its own runtime, so a naive pipeline runs QE before the async write flushes and false-grades *"Step 7
|
|
129
|
+
never ran"* on real code. For a Codex-coded run the pipeline polls a **bounded 120s backing-off window**
|
|
130
|
+
(`1,2,2,5,5,10,10,15,20,25,25`s), preferring the code stage's *declared expected files* when known, and
|
|
131
|
+
emits an explicit `changed=0 after 120s — genuinely not landed` only when the window truly expires — so
|
|
132
|
+
QE distinguishes "not implemented" from "not yet flushed". A Claude-coded run is synchronous and skips
|
|
133
|
+
the barrier entirely (zero added wait). Note: if Codex flushes slower than 120s, re-verify after the run
|
|
134
|
+
rather than trusting the end-of-run grade.
|
|
135
|
+
|
|
127
136
|
```bash
|
|
128
137
|
# Headless login on a VPS (no browser):
|
|
129
138
|
codex login --device-auth # prints a code + URL you approve on another device
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dzhechkov/skills-feature-adr",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.44",
|
|
4
4
|
"description": "Adaptive Feature Development skill pack for Claude Code — 11-step pipeline with Complexity Router (S/M/L/XL), ADR-driven architecture, 15 agentic-qe skills, multi-agent fleet QE. Supports --full-qe, --full-qe-extended, --with-learning, and --knowledge-extractor modes.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"skills-feature-adr": "./bin/cli.js"
|
|
@@ -240,6 +240,116 @@ function stageLabel(baseLabel, opts) {
|
|
|
240
240
|
// Codex-QE run, do ZERO extra work (byte-identical to today).
|
|
241
241
|
function needsLandedBarrier(opts) { return !!(opts && opts.agentType === 'codex:codex-rescue') }
|
|
242
242
|
|
|
243
|
+
const DEFAULT_CODE_LANDING_MAX_WAIT_MS = 120000
|
|
244
|
+
const DEFAULT_CODE_LANDING_BACKOFF_MS = [1000, 2000, 2000, 5000, 5000, 10000, 10000, 15000, 20000, 25000, 25000]
|
|
245
|
+
const CODE_LANDED_BARRIER_SLEEPS_SECONDS = DEFAULT_CODE_LANDING_BACKOFF_MS.map((ms) => ms / 1000)
|
|
246
|
+
const CODE_LANDING_PIPELINE_PREFIXES = ['features/', '.dz/', '.agentic-qe/', 'roam/']
|
|
247
|
+
|
|
248
|
+
function codeLandingEmptySignal(seconds) {
|
|
249
|
+
return 'changed=0 after ' + seconds + 's — genuinely not landed'
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function needsCodeLandedBarrier(coderUsed) {
|
|
253
|
+
return coderUsed === 'codex' || coderUsed === 'codex-fallback'
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function normalizeCodeLandingPath(path) {
|
|
257
|
+
let p = String(path || '').trim().replace(/\\/g, '/')
|
|
258
|
+
while (p.indexOf('./') === 0) p = p.slice(2)
|
|
259
|
+
p = p.replace(/\/+/g, '/')
|
|
260
|
+
if (!p) return ''
|
|
261
|
+
if (p[0] === '/') return ''
|
|
262
|
+
if (p === '..' || p.indexOf('../') === 0 || p.indexOf('/../') >= 0 || p.endsWith('/..')) return ''
|
|
263
|
+
if (/[\0\r\n\t "'\x60$;&|<>*?()[\]{}!]/.test(p)) return ''
|
|
264
|
+
if (p.endsWith('/')) return ''
|
|
265
|
+
for (let i = 0; i < CODE_LANDING_PIPELINE_PREFIXES.length; i++) {
|
|
266
|
+
const prefix = CODE_LANDING_PIPELINE_PREFIXES[i]
|
|
267
|
+
const bare = prefix.slice(0, -1)
|
|
268
|
+
if (p === bare || p.indexOf(prefix) === 0) return ''
|
|
269
|
+
}
|
|
270
|
+
return p
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function filterPollableCodePaths(paths) {
|
|
274
|
+
const out = []
|
|
275
|
+
const seen = {}
|
|
276
|
+
for (let i = 0; i < (paths || []).length; i++) {
|
|
277
|
+
const normalized = normalizeCodeLandingPath(paths[i])
|
|
278
|
+
if (!normalized || seen[normalized]) continue
|
|
279
|
+
seen[normalized] = 1
|
|
280
|
+
out.push(normalized)
|
|
281
|
+
}
|
|
282
|
+
return out
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function codeLandedBarrierPlan(coderUsed, expectedPaths) {
|
|
286
|
+
const enabled = needsCodeLandedBarrier(coderUsed)
|
|
287
|
+
const pollWindowSeconds = DEFAULT_CODE_LANDING_MAX_WAIT_MS / 1000
|
|
288
|
+
if (!enabled) {
|
|
289
|
+
return { enabled: false, mode: 'any-code-change', sleepsMs: [], sleepsSeconds: [], pollWindowMs: 0, pollWindowSeconds: 0, expectedPaths: [], emptySignal: '' }
|
|
290
|
+
}
|
|
291
|
+
const filteredExpectedPaths = filterPollableCodePaths(expectedPaths || [])
|
|
292
|
+
return {
|
|
293
|
+
enabled: true,
|
|
294
|
+
mode: filteredExpectedPaths.length > 0 ? 'expected-files' : 'any-code-change',
|
|
295
|
+
sleepsMs: DEFAULT_CODE_LANDING_BACKOFF_MS,
|
|
296
|
+
sleepsSeconds: CODE_LANDED_BARRIER_SLEEPS_SECONDS,
|
|
297
|
+
pollWindowMs: DEFAULT_CODE_LANDING_MAX_WAIT_MS,
|
|
298
|
+
pollWindowSeconds: pollWindowSeconds,
|
|
299
|
+
expectedPaths: filteredExpectedPaths,
|
|
300
|
+
emptySignal: codeLandingEmptySignal(pollWindowSeconds),
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function addExpectedCodeTarget(value, out) {
|
|
305
|
+
if (value === null || value === undefined) return
|
|
306
|
+
if (Array.isArray(value)) {
|
|
307
|
+
for (let i = 0; i < value.length; i++) addExpectedCodeTarget(value[i], out)
|
|
308
|
+
return
|
|
309
|
+
}
|
|
310
|
+
if (typeof value === 'object') {
|
|
311
|
+
if (Array.isArray(value.wrote)) addExpectedCodeTarget(value.wrote, out)
|
|
312
|
+
if (Array.isArray(value.paths)) addExpectedCodeTarget(value.paths, out)
|
|
313
|
+
return
|
|
314
|
+
}
|
|
315
|
+
const lines = String(value).split(/\r?\n/)
|
|
316
|
+
for (let i = 0; i < lines.length; i++) {
|
|
317
|
+
const candidate = lines[i].replace(/^[-*]\s+/, '').replace(/^\x60+|\x60+$/g, '').trim()
|
|
318
|
+
if (candidate) out.push(candidate)
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function extractExpectedCodeTargetsFromText(text) {
|
|
323
|
+
const out = []
|
|
324
|
+
const lines = String(text || '').split(/\r?\n/)
|
|
325
|
+
let inBlock = false
|
|
326
|
+
for (let i = 0; i < lines.length; i++) {
|
|
327
|
+
const trimmed = lines[i].trim()
|
|
328
|
+
if (/^EXPECTED_CODE_TARGETS:\s*$/i.test(trimmed)) { inBlock = true; continue }
|
|
329
|
+
if (!inBlock) continue
|
|
330
|
+
if (!trimmed) continue
|
|
331
|
+
if (/^[A-Z][A-Z0-9_ -]*:\s*$/.test(trimmed)) break
|
|
332
|
+
out.push(trimmed.replace(/^[-*]\s+/, '').replace(/^\x60+|\x60+$/g, '').trim())
|
|
333
|
+
}
|
|
334
|
+
return out
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function extractExpectedCodeTargets(argTargets, codexText) {
|
|
338
|
+
const out = []
|
|
339
|
+
addExpectedCodeTarget(argTargets, out)
|
|
340
|
+
addExpectedCodeTarget(extractExpectedCodeTargetsFromText(codexText), out)
|
|
341
|
+
return filterPollableCodePaths(out)
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function codeLandingShellQuote(value) {
|
|
345
|
+
return "'" + String(value).replace(/'/g, "'\"'\"'") + "'"
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function codeLandingProbeCmd(repo, plan) {
|
|
349
|
+
const expectedList = plan.expectedPaths.length > 0 ? plan.expectedPaths.map(codeLandingShellQuote).join(' ') : "''"
|
|
350
|
+
return 'repo=' + codeLandingShellQuote(repo) + '; sleeps="' + plan.sleepsSeconds.join(' ') + '"; expected_count=' + plan.expectedPaths.length + '; elapsed=0; poll(){ paths=$(git -C "$repo" status --porcelain 2>/dev/null | sed -E "s/^...//" | sed -E "s/.* -> //" | grep -vE "^(features/|[.]dz/|[.]agentic-qe/|roam/)" | sed "/^$/d" | head -200); n=$(printf "%s\\n" "$paths" | sed "/^$/d" | wc -l | tr -d " "); if [ "$expected_count" -gt 0 ]; then matched=""; for p in ' + expectedList + '; do [ -z "$p" ] && continue; if printf "%s\\n" "$paths" | grep -Fx -- "$p" >/dev/null; then matched="$p"; break; fi; done; if [ -n "$matched" ]; then echo "CODEX-LANDING-SIGNAL status=landed changed=1 after=${elapsed}s predicate=expected-path"; echo "matched=$matched"; echo "files:"; printf "%s\\n" "$paths" | head -40; exit 0; fi; else if [ "$n" -gt 0 ]; then echo "CODEX-LANDING-SIGNAL status=landed changed=$n after=${elapsed}s predicate=any-code-change"; echo "files:"; printf "%s\\n" "$paths" | head -40; exit 0; fi; fi; }; poll; for wait in $sleeps; do sleep "$wait"; elapsed=$((elapsed + wait)); poll; done; echo "CODEX-LANDING-SIGNAL status=genuinely-not-landed ' + plan.emptySignal + '"; if [ "$expected_count" -gt 0 ]; then echo "predicate=expected-path observed=$n"; else echo "predicate=any-code-change"; fi; echo "files:"; if [ -n "$paths" ]; then printf "%s\\n" "$paths" | head -40; else echo "(none)"; fi'
|
|
351
|
+
}
|
|
352
|
+
|
|
243
353
|
// A Bash one-liner that waits for a Codex OUT-OF-BAND artifact write to LAND: polls up to ~40s until
|
|
244
354
|
// the file exists, is non-empty, AND its size is stable across two reads (write finished). Assumes a
|
|
245
355
|
// fresh feature slug (no stale same-path artifact) — true for a normal /feature-adr run.
|
|
@@ -383,6 +493,7 @@ const codeClaudeModel = codeIsCodexFirst ? {} : (codeModel.agentType ? {} : code
|
|
|
383
493
|
const codeClaudeOpts = mergeOpts({ label: stageLabel('code', codeClaudeModel), phase: 'Code', schema: ARTIFACT, effort: 'high' }, codeClaudeModel)
|
|
384
494
|
let code = null
|
|
385
495
|
let coderUsed = 'claude'
|
|
496
|
+
let codexCodeText = ''
|
|
386
497
|
if (!codeIsCodexFirst) {
|
|
387
498
|
code = await agent(codePrompt, codeClaudeOpts)
|
|
388
499
|
if (code) { coderUsed = 'claude'; modelsUsed.code = modelLabel(codeClaudeOpts) }
|
|
@@ -392,20 +503,22 @@ if (code === null && (codeIsCodexFirst || CODER === 'codex-fallback')) {
|
|
|
392
503
|
if (CODER === 'codex-fallback' && !codeIsCodexFirst) log('Code: Claude unavailable (limit?) — falling back to Codex ' + CODEX_MODEL)
|
|
393
504
|
const codeCodexLabelOpts = codeModel.agentType ? codeModel : specToOpts('codex:' + CODEX_MODEL + ':high')
|
|
394
505
|
const codeCodexOpts = mergeOpts({ label: stageLabel('code:codex', codeCodexLabelOpts), phase: 'Code', agentType: 'codex:codex-rescue' }, codeModel.agentType ? codeModel : {})
|
|
395
|
-
const
|
|
396
|
-
|
|
506
|
+
const codexExpectedTargetsHint = '\n\nBecause this is running on Codex, include a final EXPECTED_CODE_TARGETS: block listing the repo-relative production/test files you expect to create or modify. List only real code/test/config/docs targets outside features/, .dz/, .agentic-qe/, and roam/. Example:\nEXPECTED_CODE_TARGETS:\n- packages/example/src/file.ts\n- packages/example/test/file.test.ts'
|
|
507
|
+
const codexCode = await agent(codePrompt + CODEX_HINT + codexExpectedTargetsHint, codeCodexOpts)
|
|
508
|
+
if (codexCode) { codexCodeText = String(codexCode); code = { wrote: [FDIR + '/07_code_changes/change_manifest.md'], summary: codexCodeText.slice(0, 500) }; coderUsed = codeIsCodexFirst ? 'codex' : 'codex-fallback'; modelsUsed.code = modelLabel(codeCodexLabelOpts) }
|
|
397
509
|
}
|
|
398
510
|
|
|
399
511
|
// Step 7.5: Codex-landed barrier. Codex applies edits OUT-OF-BAND via its own runtime; without this,
|
|
400
|
-
// Step-8 QE
|
|
401
|
-
//
|
|
402
|
-
//
|
|
403
|
-
// reviews the ACTUAL landed changes. Claude-coded runs are synchronous → this barrier is skipped.
|
|
512
|
+
// Step-8 QE can read the tree before the async write flushes and false-grade "Step 7 never ran" on real
|
|
513
|
+
// completed code. Poll a bounded 120s backing-off window, preferring declared expected code targets when
|
|
514
|
+
// known. Claude-coded runs are synchronous → this barrier is skipped with zero target parsing/probe work.
|
|
404
515
|
let landedNote = ''
|
|
405
|
-
if (coderUsed
|
|
406
|
-
const
|
|
407
|
-
const
|
|
408
|
-
|
|
516
|
+
if (needsCodeLandedBarrier(coderUsed)) {
|
|
517
|
+
const expectedCodeTargets = extractExpectedCodeTargets(A.expectedCodeTargets, codexCodeText)
|
|
518
|
+
const barrierPlan = codeLandedBarrierPlan(coderUsed, expectedCodeTargets)
|
|
519
|
+
const barrierCmd = codeLandingProbeCmd(REPO, barrierPlan)
|
|
520
|
+
const probe = await agent('Confirm the Codex Step-7 edits have LANDED in the working tree BEFORE QE runs (Codex writes out-of-band). Expected-file mode must be satisfied by one of the declared expected paths; unrelated dirty files do not count in that mode. Run EXACTLY this via Bash and return its stdout verbatim, nothing else:\n' + barrierCmd, { label: 'code:confirm-landed', phase: 'Code' })
|
|
521
|
+
landedNote = '\n\nCODEX-CODED (out-of-band): Step 7.5 landing barrier used mode=' + barrierPlan.mode + ', window=' + barrierPlan.pollWindowSeconds + 's. Review the signal below. If status=landed, read the listed files and do NOT report "Step 7 never ran". Only status=genuinely-not-landed with "' + barrierPlan.emptySignal + '" means the bounded barrier found no intended code after the full window.\nExpected code targets: ' + (barrierPlan.expectedPaths.length ? barrierPlan.expectedPaths.join(', ') : '(none declared; fallback accepts any non-pipeline code change)') + '\n' + String(probe || '(landed-probe failed)').slice(0, 1500)
|
|
409
522
|
}
|
|
410
523
|
|
|
411
524
|
// Step 8: QE (brutal-honesty, agentic-qe) + MANDATORY teach
|