feature-factory 0.10.0 → 0.10.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/README.md +11 -0
- package/WORKFLOW.md +106 -40
- package/bin/factory.js +189 -34
- package/bin/restore.js +26 -7
- package/bin/snapshot.js +130 -0
- package/core/contracts.js +48 -7
- package/core/run-lock.js +14 -4
- package/core/write-core.js +1 -1
- package/package.json +1 -1
- package/state/review-archive.js +36 -22
- package/state/schema.js +85 -4
package/README.md
CHANGED
|
@@ -258,6 +258,17 @@ own the same path. Duplicate, target-already-owned, malformed, privileged, repla
|
|
|
258
258
|
requests refuse atomically. Resume never amends or reseeds. A merge continues to refuse every unamended
|
|
259
259
|
or privileged changed path.
|
|
260
260
|
|
|
261
|
+
A parked slice that exhausted its effective limit can receive exactly one audited extension through
|
|
262
|
+
`factory grant-retry <run-id> <slice-id> --scope slice|all --reason <text> --session <id> --repo <sandbox>`.
|
|
263
|
+
Slice scope raises only the target's additive allowance. All scope raises `max_retries` for pending later
|
|
264
|
+
waves too, but refuses when another slice is blocked or an exhausted post-merge repair record exists. Both
|
|
265
|
+
scopes reopen only the named slice after exact owner, snapshot, REJECT, evidence, base, current clean head,
|
|
266
|
+
and immutable attempt archives. A legacy run missing one prepares it without granting, then requires a
|
|
267
|
+
fresh snapshot and a second grant invocation. Exhaustion parks with reason `blocked-after-retries` rather
|
|
268
|
+
than terminalizing `partial`. The grant moves the prior canonical snapshot away, so restore cannot recover
|
|
269
|
+
pre-grant authority; run `factory snapshot <run-id> --repo <operator>` and requalify it before the separate
|
|
270
|
+
explicit resume. Restored blocked slices whose physical refs were intentionally cleared do not qualify.
|
|
271
|
+
|
|
261
272
|
`resolve` and `verify` are consumed now, and the run's recorded `publishing_identity` is compared at the publication guards. Step 6 resolves one selection: a nonblank inherited `FACTORY_PUBLISHING_COMMAND` selects its exact string; that variable set blank or whitespace selects the default; when it is unset, configured `publish` wins if present; otherwise the default wins. Only a selected nondefault command replaces `gh pr create`, after the factory-owned exact push and post-push identity guard. It receives exact `PR_BASE`, `FEATURE_BRANCH`, `PR_DRAFT`, `PR_TITLE`, and absolute `PR_BODY_FILE` environment values. Only exit zero with an absolute HTTPS URL on the last nonempty stdout line is recordable; every other result parks with exact reason `selected publishing command outcome indeterminate; re-observe whether the pull request exists before retry` and no fallback.
|
|
262
273
|
Effective push-target capture and comparison are active through the package-owned `factory effective-push` command; they are not deferred to configured `publish`.
|
|
263
274
|
The recorded `publishing_identity` is read from `status` exactly as reported, without trimming,
|
package/WORKFLOW.md
CHANGED
|
@@ -209,9 +209,40 @@ sequence with `UNKNOWN_OUTCOME_REASON`. Never repeat successful siblings.
|
|
|
209
209
|
A budgeted attempt advances only after a complete specialist response reaches the ordinary workflow and
|
|
210
210
|
that response is rejected on its merits or violates the specialist's required output contract. A complete
|
|
211
211
|
unbudgeted result returns to its ordinary workflow without creating an attempt. Infrastructure recovery is
|
|
212
|
-
the same attempt, not another use of `max_retries`.
|
|
213
|
-
|
|
214
|
-
|
|
212
|
+
the same attempt, not another use of `max_retries`. For a slice, an output-contract violation may advance
|
|
213
|
+
only when canonical evidence and its matching REJECT review were recorded; if the malformed output prevents
|
|
214
|
+
either record, park top-level `needs-human` through the common procedure instead of fabricating authority for
|
|
215
|
+
N+1. This rule is instruction rather than CLI enforcement: the host owns specialist invocation errors, while
|
|
216
|
+
the CLI continues to enforce every durable attempt transition the driver actually records.
|
|
217
|
+
|
|
218
|
+
### Operator-authorized retry extension
|
|
219
|
+
|
|
220
|
+
A slice that reached its effective retry limit remains terminal until an operator explicitly grants exactly
|
|
221
|
+
one more attempt. Never edit `run.json`. Keep the top-level run in its existing parked state, claim and verify its
|
|
222
|
+
fresh session lock, record a concrete reason why N+1 is now bounded and materially different, and choose one
|
|
223
|
+
scope deliberately:
|
|
224
|
+
|
|
225
|
+
```sh
|
|
226
|
+
factory grant-retry "$R" "$SLICE_ID" --scope slice --reason "$EXTENSION_REASON" --session "$SESSION_ID" --repo "$RUN_REPO"
|
|
227
|
+
factory grant-retry "$R" "$SLICE_ID" --scope all --reason "$EXTENSION_REASON" --session "$SESSION_ID" --repo "$RUN_REPO"
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
`slice` raises only that slice's additive allowance. `all` raises the run-wide default, including every
|
|
231
|
+
pending later wave, but still reopens only `SLICE_ID`; it refuses while another slice is blocked or an
|
|
232
|
+
exhausted post-merge repair exists. Both scopes require `blocked@N` exactly at the current effective limit;
|
|
233
|
+
a matching REJECT, evidence, immutable base and live clean branch head; the exact fresh lock owner; a
|
|
234
|
+
complete current park snapshot; and immutable attempt-N review and evidence archives. A legacy run missing
|
|
235
|
+
an archive gets a preparation-only refusal: publish the changed plane and invoke the grant again. They
|
|
236
|
+
append the durable authorization, preserve worktree, branch and `base_ref`, clear only the live attempt-bound
|
|
237
|
+
refs, and record `running@(N+1)` while top-level status and `terminal_result` remain parked.
|
|
238
|
+
|
|
239
|
+
The grant atomically moves the old canonical snapshot away so restore cannot recover pre-grant authority.
|
|
240
|
+
Do not dispatch or resume yet. Republish the updated live plane, then require qualified status
|
|
241
|
+
to report the refreshed `park_snapshot`, unchanged owner, the chosen new effective limit, and only the named
|
|
242
|
+
slice at `running@(N+1)`. Only then run the ordinary explicit
|
|
243
|
+
`factory resume "$R" --session "$SESSION_ID" --repo "$RUN_REPO"` and dispatch that attempt. Resume refreshes
|
|
244
|
+
the staged workflow before its final snapshot check. A grant never invokes a specialist, unlocks, resumes,
|
|
245
|
+
approves, merges, or publishes.
|
|
215
246
|
|
|
216
247
|
## The chain
|
|
217
248
|
|
|
@@ -379,6 +410,12 @@ sandbox: the completed handoff is the only thing that archives it, and that hand
|
|
|
379
410
|
`completed`. So anything that removed the sandbox destroyed the manifest, the approved gates, the ratified
|
|
380
411
|
plan and every review verdict, leaving the run neither resumable nor reconstructable.
|
|
381
412
|
|
|
413
|
+
`factory snapshot "$R" --repo "$O" --json` performs this publication and is the supported way to do it.
|
|
414
|
+
The steps below remain the definition of what it does; a driver may run the command instead of carrying
|
|
415
|
+
them out itself, and a supervisor that parked a run out-of-band **must** run it, because `factory
|
|
416
|
+
terminal` alone completes step 1 of the park and leaves no recovery evidence. It refuses a run that is
|
|
417
|
+
not parked, so it cannot record a live plane as a snapshot of a moment no resume can return to.
|
|
418
|
+
|
|
382
419
|
Publish the live plane `P` to `$O/.factory/.parked/$R`. Inspect `$O/.factory` and `$O/.factory/.parked` with
|
|
383
420
|
non-following metadata reads, creating each missing parent one directory at a time and requiring any present
|
|
384
421
|
one to be a real directory rather than a symbolic link. Never write through a symlinked parent, and never
|
|
@@ -396,12 +433,11 @@ and "clean up the prior copy" are contradictory instructions once that rename ha
|
|
|
396
433
|
outside `P`; do not copy slice worktrees or any other part of `S`.
|
|
397
434
|
3. **Verify.** Build source and destination inventories exactly as the completed archive does — every
|
|
398
435
|
entry's relative path, type and mode, a SHA-256 for each regular file, a link target for each symlink,
|
|
399
|
-
sorted lexically — and require exact equality,
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
the
|
|
404
|
-
reason. An unverified staging tree is never published.
|
|
436
|
+
sorted lexically — and require exact equality, excluding only plane-root `factory.lock` and
|
|
437
|
+
`run-json.lock`. The first is session liveness and can change on a timer; the second is held by the
|
|
438
|
+
snapshot command to serialize publication with state transitions. The exclusions are those exact root
|
|
439
|
+
paths and nothing else: either name below the plane root is run state and must match. Qualified status
|
|
440
|
+
applies the same exact exclusions at a transition boundary. An unverified staging tree is never published.
|
|
405
441
|
4. **Commit.** With no snapshot at the canonical path, rename `.staging-$R` onto it; that rename is the
|
|
406
442
|
commit point. With one present, first rename the canonical snapshot to `.prior-$R`, then rename
|
|
407
443
|
`.staging-$R` onto the canonical path; that second rename is the commit point. If the first rename
|
|
@@ -1413,11 +1449,16 @@ For a fresh pending slice, set the exact names, require both `refs/heads/$SLICE_
|
|
|
1413
1449
|
`SLICE_WORKTREE` path to be absent, and create the worktree from the current feature branch before
|
|
1414
1450
|
activation:
|
|
1415
1451
|
|
|
1452
|
+
Before creating a pending slice worktree, reload its exact manifest row. Bind `ACTIVATION_START` to
|
|
1453
|
+
`FEATURE_BRANCH` when `base_ref` is null. When a restored retry-extension row preserves non-null `base_ref`,
|
|
1454
|
+
require its latest audit to name the same base and start the replacement slice branch at that exact historical
|
|
1455
|
+
base. This recreates the original retry branch without importing later sibling changes into its owned diff.
|
|
1456
|
+
|
|
1416
1457
|
```sh
|
|
1417
1458
|
SLICE_BRANCH="factory/$R/$SLICE_ID"
|
|
1418
1459
|
SLICE_WORKTREE="$SLICE_ROOT/$SLICE_ID"
|
|
1419
1460
|
CHECKED_OUT_FEATURE_BRANCH="$(git -C "$INTEGRATION_WORKTREE" symbolic-ref --quiet --short HEAD)"
|
|
1420
|
-
git -C "$RUN_REPO" worktree add -b "$SLICE_BRANCH" "$SLICE_WORKTREE" "$
|
|
1461
|
+
git -C "$RUN_REPO" worktree add -b "$SLICE_BRANCH" "$SLICE_WORKTREE" "$ACTIVATION_START"
|
|
1421
1462
|
$ factory slice "$R" "$SLICE_ID" running --worktree "$SLICE_WORKTREE" --branch "$SLICE_BRANCH" --repo "$RUN_REPO"
|
|
1422
1463
|
```
|
|
1423
1464
|
|
|
@@ -1431,6 +1472,8 @@ Step 0. Require `run_id === R`, select exactly one `slices` row with `id === SLI
|
|
|
1431
1472
|
|
|
1432
1473
|
```text
|
|
1433
1474
|
RECORDED_SLICE = parsedRun.slices row whose id equals SLICE_ID
|
|
1475
|
+
MAX_RETRIES = parsedRun.max_retries
|
|
1476
|
+
SLICE_RETRY_LIMIT = MAX_RETRIES + (RECORDED_SLICE.extra_attempts when present, otherwise 0)
|
|
1434
1477
|
SLICE_WORKTREE = RECORDED_SLICE.worktree
|
|
1435
1478
|
SLICE_BRANCH = RECORDED_SLICE.branch
|
|
1436
1479
|
SLICE_BASE_REF = RECORDED_SLICE.base_ref
|
|
@@ -1444,13 +1487,15 @@ for it. A driver that assumes "this is the first try" observes as attempt 1 whil
|
|
|
1444
1487
|
merge then refuses that evidence — `evidence '…' is for attempt 1, slice is at attempt 2` — after the build
|
|
1445
1488
|
and the review have already been spent. It names the report and the `--attempt` argument below.
|
|
1446
1489
|
|
|
1447
|
-
Require
|
|
1448
|
-
a
|
|
1449
|
-
|
|
1450
|
-
`
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1490
|
+
Require every bound value to be non-null, `MAX_RETRIES`, `SLICE_RETRY_LIMIT`, and `SLICE_ATTEMPT` to be positive integers,
|
|
1491
|
+
`SLICE_BASE_REF` to be a 40-character commit SHA, `SLICE_BRANCH` to equal `factory/R/<slice-id>`, and the
|
|
1492
|
+
physical `SLICE_WORKTREE` to equal `SLICE_ROOT/<slice-id>`. Require `git -C "$RUN_REPO" worktree list
|
|
1493
|
+
--porcelain` to associate that physical path with that exact branch. Before dispatch or observation require
|
|
1494
|
+
status `running`. A `review` row is a completed decision checkpoint: on resume consume its recorded review
|
|
1495
|
+
through step 4, never re-observe it. A pending slice requires path and branch ref to remain absent. Its
|
|
1496
|
+
base is absent unless a restored retry-extension audit preserves that immutable base; any unrecorded path
|
|
1497
|
+
or branch ref is a collision. Refuse every mismatch instead of repairing, deleting, or
|
|
1498
|
+
reassociating it. A merged slice is never dispatched again.
|
|
1454
1499
|
|
|
1455
1500
|
For a non-empty `SLICE_TEST_PLAN`, select one complete entry and bind `SLICE_TEST_COMMAND` by copying
|
|
1456
1501
|
that persisted string verbatim. Never shorten, append to, normalize, or source it from the mutable
|
|
@@ -1504,31 +1549,32 @@ Per slice:
|
|
|
1504
1549
|
is no repair available at this step.** The slice is already activated, so `base_ref` is fixed; the suite
|
|
1505
1550
|
runs in `SLICE_WORKTREE`, so a commit on the integration branch is invisible to the re-observation; and
|
|
1506
1551
|
bringing that commit into the slice would put an out-of-lane test path in the observed diff, which the
|
|
1507
|
-
merge refuses.
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1552
|
+
merge refuses. This is a ratified-plan conflict, not a merit REJECT: do not mark the slice `blocked`
|
|
1553
|
+
without canonical evidence and a matching max-attempt review. Stop dispatching its dependents and park
|
|
1554
|
+
top-level `needs-human` through the common procedure with the diagnosis below. Retain merged siblings on
|
|
1555
|
+
the integration branch and the whole sandbox for operator replanning. The parked run is **surfaced, not
|
|
1556
|
+
published**: Gate 3 refuses the approval that authorizes publication unless every slice is `merged`, so
|
|
1557
|
+
an operator decides what to do with the work rather than a PR appearing for a plan that did not finish.
|
|
1512
1558
|
|
|
1513
1559
|
**Never narrow the ratified command to get past this.** `factory observe` compares the raw supplied
|
|
1514
1560
|
slice command with the persisted ratified entries before tokenization or execution and refuses a
|
|
1515
|
-
shortened, appended, or normalized command without writing evidence. A narrowed command is a false green wearing evidence's clothes;
|
|
1561
|
+
shortened, appended, or normalized command without writing evidence. A narrowed command is a false green wearing evidence's clothes; parking for replanning is the honest outcome when the verbatim command fails.
|
|
1516
1562
|
|
|
1517
1563
|
If the same incompatibility instead first appears in the **integrated** suite, this step is not involved
|
|
1518
1564
|
at all — Step 5's NO-GO repair owns it, on the branch where that suite actually runs.
|
|
1519
1565
|
|
|
1520
|
-
When you
|
|
1521
|
-
`--reason`: which slice owns the test, which assertion cannot hold, and what would
|
|
1522
|
-
reason naming only "tests failed" makes the operator repeat the whole investigation,
|
|
1523
|
-
difference between their fix being one commit and being an afternoon.
|
|
1566
|
+
When you park this plan conflict, record the **diagnosis** and not just the failure in the park
|
|
1567
|
+
terminal transition's `--reason`: which slice owns the test, which assertion cannot hold, and what would
|
|
1568
|
+
make it hold. A reason naming only "tests failed" makes the operator repeat the whole investigation,
|
|
1569
|
+
which is the difference between their fix being one commit and being an afternoon.
|
|
1524
1570
|
|
|
1525
1571
|
An out-of-lane **production** change is a different thing entirely and follows **Ownership disclosure**
|
|
1526
1572
|
below, where the reviewer decides whether the plan or the change is wrong.
|
|
1527
1573
|
4. **Review** — `work-reviewer` with subject `<slice-id>`, the observed evidence, the slice spec, and
|
|
1528
1574
|
the brief. Record both refs — the merge requires each:
|
|
1529
1575
|
```sh
|
|
1530
|
-
$ factory slice "$R" "$SLICE_ID" review --
|
|
1531
|
-
--review-ref "reviews/$SLICE_ID.json" --repo "$RUN_REPO"
|
|
1576
|
+
$ factory slice "$R" "$SLICE_ID" review --attempts "$SLICE_ATTEMPT" \
|
|
1577
|
+
--evidence-ref "evidence/$SLICE_ID.json" --review-ref "reviews/$SLICE_ID.json" --repo "$RUN_REPO"
|
|
1532
1578
|
```
|
|
1533
1579
|
- On REJECT, before spending an attempt, identify the cause of the remaining failures. If the fix would
|
|
1534
1580
|
violate an approved story or brief constraint, or repeated findings trace to the same unresolved
|
|
@@ -1538,8 +1584,26 @@ Per slice:
|
|
|
1538
1584
|
If no such target can be identified, park through the existing parked-stop procedure for replanning
|
|
1539
1585
|
or operator clarification; preserve the work and do not silently change approved acceptance criteria.
|
|
1540
1586
|
Unchanged finding counts alone are not a stall: progress can occur within a category that remains open.
|
|
1541
|
-
|
|
1542
|
-
`
|
|
1587
|
+
Only a complete merit REJECT spends an attempt; infrastructure recovery and resume preserve
|
|
1588
|
+
`SLICE_ATTEMPT` under the common rules above. Reload `RUN_MANIFEST`, require the recorded review to name
|
|
1589
|
+
this slice and `SLICE_ATTEMPT` with exact verdict `REJECT`, and require the row still to be `review`.
|
|
1590
|
+
If `SLICE_ATTEMPT >= SLICE_RETRY_LIMIT`, record the terminal slice state and stop dispatching its
|
|
1591
|
+
dependents without creating another attempt:
|
|
1592
|
+
```sh
|
|
1593
|
+
$ factory slice "$R" "$SLICE_ID" blocked --attempts "$SLICE_ATTEMPT" --repo "$RUN_REPO"
|
|
1594
|
+
```
|
|
1595
|
+
Then enter the common parked-stop procedure with exact reason `blocked-after-retries`; do not
|
|
1596
|
+
terminalize `partial`. The parked snapshot and retained lock are what make a later audited grant
|
|
1597
|
+
reachable without weakening the terminal slice transition.
|
|
1598
|
+
Otherwise bind `NEXT_SLICE_ATTEMPT = SLICE_ATTEMPT + 1` and, before redispatch, record:
|
|
1599
|
+
```sh
|
|
1600
|
+
$ factory slice "$R" "$SLICE_ID" running --attempts "$NEXT_SLICE_ATTEMPT" --repo "$RUN_REPO"
|
|
1601
|
+
```
|
|
1602
|
+
This retry-opening command takes no worktree, branch, evidence, or review flag. Reload the row and
|
|
1603
|
+
require status `running`, attempt `NEXT_SLICE_ATTEMPT`, unchanged worktree, branch, and exact
|
|
1604
|
+
`SLICE_BASE_REF`, and null evidence and review refs. The base is the immutable original branch point,
|
|
1605
|
+
not merely any ancestor and not the integration head after sibling merges. Then set `SLICE_ATTEMPT` to
|
|
1606
|
+
the recorded new value, route the bounded fixes back to that builder, and re-observe.
|
|
1543
1607
|
5. **Merge (you, serially)** — on APPROVE, merge the slice branch into the feature branch one at a
|
|
1544
1608
|
time. Builds are concurrent; merges are single-writer, which is what makes the parallelism safe.
|
|
1545
1609
|
```sh
|
|
@@ -1857,10 +1921,12 @@ content on those paths matches what was reviewed, so unreviewed content inside *
|
|
|
1857
1921
|
while movement around it is not. What guards the branch as a whole is the integration pass: the
|
|
1858
1922
|
validator judges the whole diff and Gate 3 will not approve unless the head it judged is still the head.
|
|
1859
1923
|
|
|
1860
|
-
Advance waves until all slices are `merged`, or a slice is `blocked`.
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
Use terminal needs-human only to park a running envelope; use explicit factory resume after the cause is fixed.
|
|
1924
|
+
Advance waves until all slices are `merged`, or a slice is `blocked`. A blocked slice stops the wave and
|
|
1925
|
+
enters the common parked-stop procedure as top-level `needs-human`; retry exhaustion never terminalizes the
|
|
1926
|
+
run as `partial`. Use explicit factory resume only after the cause is fixed or a qualified retry grant was
|
|
1927
|
+
recorded and the updated plane was published. Use terminal needs-human only to park a running envelope; use explicit factory resume after the cause is fixed.
|
|
1928
|
+
A `partial` run is **surfaced, not published** when some other checked terminal cause creates one; retry
|
|
1929
|
+
exhaustion uses the parked path above instead.
|
|
1864
1930
|
A top-level needs-human sandbox stays retained while parked and continues only after explicit factory resume.
|
|
1865
1931
|
A `blocked` or `partial` sandbox run retains `RUN_REPO`; stale nonterminal locks retain it
|
|
1866
1932
|
too. Nothing removes any of those sandboxes automatically. Legacy runs
|
|
@@ -2380,11 +2446,11 @@ Never re-do a side effect the manifest shows already done — ticket creation, p
|
|
|
2380
2446
|
Specialists are read-only toward them and builders write code only inside the worktree they receive.
|
|
2381
2447
|
- **Never hand-write `run.json`.** If a `factory` command refuses a transition, the refusal is the
|
|
2382
2448
|
answer; do not work around it by editing state.
|
|
2383
|
-
- **Bounded loops.**
|
|
2384
|
-
`
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2449
|
+
- **Bounded loops.** Each slice is bounded by `max_retries + extra_attempts`; each step uses
|
|
2450
|
+
`max_retries`. On slice exhaustion mark it `blocked` and park top-level needs-human; do not terminalize
|
|
2451
|
+
`partial` solely for retry exhaustion. Explicit resume may repark if the external cause remains unfixed.
|
|
2452
|
+
Qualified status reports the run's `max_retries` and each slice's effective `retry_limit`, so an
|
|
2453
|
+
extended budget is observed rather than assumed.
|
|
2388
2454
|
- **Publish a PR and stop.** Never merge, force-push, or close tickets. Humans merge. Draft or
|
|
2389
2455
|
ready-for-review is `pr_draft`'s decision, not this rule's.
|
|
2390
2456
|
- **Scope discipline and no fabrication.** Flag out-of-scope work at the next gate. Never invent paths,
|