feature-factory 0.7.0 → 0.7.1

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/WORKFLOW.md CHANGED
@@ -887,7 +887,14 @@ The first successful seed is the **ratification point** for two decisions:
887
887
  per-merge proof and verification are what keep that safe.
888
888
  - `test_plan` — the exact executable commands authorized to prove the slice. Each non-empty entry is
889
889
  one complete, independently sufficient command string that must be supplied verbatim as one
890
- `--test-cmd` value. A slice with a non-empty `test_plan` is not `review_ready` until one ratified
890
+ `--test-cmd` value. Each entry is executed as argv split on single spaces with no shell, so a shell
891
+ operator, a quote that groups an argument, a substitution or a redirection is inert payload or a hard
892
+ failure rather than syntax, and seeding refuses an entry it cannot execute that way. Entries
893
+ are alternatives, not a sequence: a slice needing several commands to run in order names one script
894
+ committed in the repository. `argv[0]` is resolved against the repository when the plan is seeded, before
895
+ any slice has implemented anything, so a script the work itself creates cannot be `argv[0]`: name an
896
+ interpreter that already resolves and pass the script as an argument, as in `sh scripts/verify-all.sh`.
897
+ A slice with a non-empty `test_plan` is not `review_ready` until one ratified
891
898
  command exits zero. A slice with an **empty** `test_plan` is exempt. That exemption is a decision for
892
899
  the engineer at Gate 2, so decide it in the plan and present it: there is no flag that waives tests at
893
900
  observation time.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "feature-factory",
3
- "version": "0.7.0",
3
+ "version": "0.7.1",
4
4
  "description": "Durable, observed control plane for /feature runs. Host-agnostic: no opencode dependency.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -25,8 +25,20 @@ export function attemptArchiveRef(ref, attempt) {
25
25
  return join(dir === "." ? "" : dir, `${stem}.attempt-${attempt}.json`);
26
26
  }
27
27
 
28
+ // An archive has nothing to preserve: its content is already the frozen copy. Handed one anyway --
29
+ // run 1551 reported an archive path back as `--review-ref`, and the suffix was appended twice, leaving
30
+ // `spec-writer.attempt-1.attempt-1.json` beside the real archive with identical bytes. Harmless to
31
+ // state, because the live record is untouched and `createOnly` protects the genuine archive, but the
32
+ // reviews directory is the one place an operator reads to reconstruct why a run blocked, and a file
33
+ // naming an attempt of an attempt invites them to look for a second verdict that does not exist.
34
+ //
35
+ // Instruction, not enforcement, and a no-op rather than a refusal: this module must never fail the
36
+ // step that earned the verdict, which is the contract stated at the top of this file.
37
+ const ARCHIVE_REF = /\.attempt-\d+\.json$/u;
38
+
28
39
  export async function archiveReviewAttempt(runDir, ref) {
29
40
  if (typeof ref !== "string" || !ref.trim()) return null;
41
+ if (ARCHIVE_REF.test(ref)) return null;
30
42
  let record;
31
43
  try {
32
44
  record = JSON.parse(readFileSync(join(runDir, ref), "utf8"));