@pilotspace/add 1.8.0 → 1.9.0

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/tooling/add.py CHANGED
@@ -152,6 +152,47 @@ Outcome:
152
152
  """
153
153
 
154
154
 
155
+ # Fast-lane fallback: the minimal TASK.md variant (sections {0,1,3,4,5,6}; §2 + §7 dropped).
156
+ # Mirrors templates/TASK.fast.md.tmpl's section set (circuit-breaker parity); a deleted
157
+ # templates/ never hard-fails the fast lane. Keeps the trust floor: §3 freeze-flag + Status,
158
+ # §6 GATE RECORD/Outcome, §0 Anchors, §4 red-before-build, §5 Scope.
159
+ _FALLBACK_TASK_FAST = """# TASK: {title}
160
+
161
+ slug: {slug} · created: {date} · stage: {stage}
162
+ autonomy: auto
163
+ phase: ground
164
+ fast: true
165
+
166
+ ## 0 · GROUND
167
+ Touches (files · symbols):
168
+ Anchors the contract cites:
169
+
170
+ ## 1 · SPECIFY
171
+ Feature:
172
+ Must:
173
+ Reject:
174
+ Accept:
175
+ Assumptions: ⚠ <most likely wrong> — why; if wrong: <cost>
176
+
177
+ ## 3 · CONTRACT
178
+ Least-sure flag surfaced at freeze:
179
+ Status: DRAFT
180
+
181
+ ## 4 · TESTS
182
+ Plan:
183
+ Tests live in: `./tests/` · MUST run red before Build.
184
+
185
+ ## 5 · BUILD
186
+ Scope (may touch): `./src/`
187
+
188
+ ## 6 · VERIFY
189
+ Build expectations (from §1 Accept + §3 CONTRACT):
190
+ ### GATE RECORD
191
+ Outcome:
192
+ Reviewed by:
193
+ """
194
+
195
+
155
196
  # --- low-level IO (designed for failure: atomic, no silent clobber) ----------
156
197
 
157
198
  def _now() -> str:
@@ -237,13 +278,14 @@ def _templates_dir() -> Path:
237
278
  def _render_template(name: str, **subs: str) -> str:
238
279
  """Load templates/<name>.tmpl and substitute {{key}} tokens.
239
280
 
240
- Falls back to a built-in minimal template only for TASK.md.
281
+ Falls back to a built-in minimal template for TASK.md and the fast-lane TASK.fast.md.
241
282
  """
242
283
  tmpl = _templates_dir() / f"{name}.tmpl"
284
+ _fallbacks = {"TASK.md": _FALLBACK_TASK, "TASK.fast.md": _FALLBACK_TASK_FAST}
243
285
  if tmpl.exists():
244
286
  text = tmpl.read_text(encoding="utf-8")
245
- elif name == "TASK.md":
246
- text = _FALLBACK_TASK.replace("{title}", "{{title}}").replace(
287
+ elif name in _fallbacks:
288
+ text = _fallbacks[name].replace("{title}", "{{title}}").replace(
247
289
  "{slug}", "{{slug}}").replace("{date}", "{{date}}").replace("{stage}", "{{stage}}")
248
290
  else:
249
291
  text = ""
@@ -543,6 +585,80 @@ def _setup_locked(state: dict) -> bool:
543
585
  return ("setup" not in state) or (state["setup"].get("locked") is True)
544
586
 
545
587
 
588
+ def _milestone_confirmed(state: dict, mslug: str) -> bool:
589
+ """True when milestone `mslug` is confirmed — i.e. the new-task gate is OPEN.
590
+
591
+ Mirrors `_setup_locked` one level down. A milestone record with NO "confirmed" key is
592
+ GRANDFATHERED-confirmed: every milestone created WITHOUT `--await-confirm` (and every
593
+ pre-existing one) is never gated. Opt-in: `new-milestone --await-confirm` seeds confirmed:false,
594
+ so the gate is active in exactly one case: the record is present AND confirmed is False. An
595
+ unknown milestone is treated as confirmed here (existence is cmd_new_task's separate check)."""
596
+ m = (state.get("milestones") or {}).get(mslug)
597
+ if not isinstance(m, dict) or "confirmed" not in m:
598
+ return True
599
+ return m["confirmed"] is True
600
+
601
+
602
+ def _section_unfilled(md_text: str, header: str) -> bool:
603
+ """True iff the `header` section is PRESENT but UNFILLED — empty (no real bullet) or
604
+ still a `<…>` template placeholder. ABSENT section -> False (grandfathered legacy);
605
+ a filled section (>=1 real bullet, no `<…>`) -> False. Pure predicate — the shared
606
+ placeholder test the fill gates use (contract-fill at confirm; build-expectations at build)."""
607
+ body, in_sec, present = [], False, False
608
+ for ln in md_text.splitlines():
609
+ if ln.startswith(header):
610
+ in_sec, present = True, True
611
+ continue
612
+ if in_sec:
613
+ if ln.startswith("#"): # ANY next header (## or ###) ends our section
614
+ break
615
+ if ln.lstrip().startswith(">"): # skip blockquote GUIDANCE — it is not content
616
+ continue
617
+ body.append(ln)
618
+ if not present:
619
+ return False # absent -> grandfather
620
+ text = "\n".join(body).strip()
621
+ if not text:
622
+ return True # present but empty
623
+ return bool(re.search(r"<[^>\n]+>", text)) # a <…> placeholder remains
624
+
625
+
626
+ def _stamp_gate_record(root: Path, state: dict, slug: str, outcome: str) -> None:
627
+ """Write-back (gate-record-writeback): mirror the resolved gate verdict into the task's
628
+ §6 `### GATE RECORD`, so the file and state.json never silently diverge (Finding C). Runs
629
+ for EVERY task — the write is ADDITIVE and never refuses, so (unlike the two refusal gates)
630
+ it needs no `--await-confirm` opt-in to protect the census. GRANDFATHER is the safety: a
631
+ GATE RECORD line is rewritten ONLY while it still holds a `<…>` placeholder; a resolved
632
+ (hand-filled) line is byte-untouched. No GATE RECORD block / no placeholder line / an
633
+ unreadable file -> silent no-op, the file stays byte-identical. Called AFTER save_state —
634
+ state is the source of truth; the file only mirrors it, so a write fault never loses a verdict."""
635
+ f = root / "tasks" / slug / "TASK.md"
636
+ try:
637
+ text = f.read_text(encoding="utf-8")
638
+ except OSError:
639
+ return # unreadable -> no-op (never blocks the gate)
640
+ if "### GATE RECORD" not in text:
641
+ return # nothing to mirror into
642
+ actor = _actor_stamp(state)
643
+ today = date.today().isoformat()
644
+ # each rule matches ONLY a line still carrying a `<…>` placeholder -> grandfather a resolved line.
645
+ rules = [
646
+ (r"(?m)^(Outcome:[ \t]*)<[^>\n]*>.*$", f"Outcome: {outcome}"),
647
+ (r"(?m)^Reviewed by:[ \t]*.*<[^>\n]*>.*$",
648
+ f"Reviewed by: {actor['name']} · date: {today}"),
649
+ ]
650
+ if outcome == "RISK-ACCEPTED":
651
+ w = ((state.get("tasks") or {}).get(slug) or {}).get("waiver") or {}
652
+ rules.append((r"(?m)^If RISK-ACCEPTED ->.*<[^>\n]*>.*$",
653
+ f"If RISK-ACCEPTED -> owner: {w.get('owner', '?')} · "
654
+ f"ticket: {w.get('ticket', '?')} · expires: {w.get('expires', '?')}"))
655
+ new = text
656
+ for pat, repl in rules:
657
+ new = re.sub(pat, repl, new, count=1)
658
+ if new != text: # no-op = no write (mtime stable)
659
+ _atomic_write(f, new)
660
+
661
+
546
662
  def _die(msg: str, code: int = 1) -> None:
547
663
  print(f"add: error: {msg}", file=sys.stderr)
548
664
  raise SystemExit(code)
@@ -763,6 +879,12 @@ def cmd_new_task(args: argparse.Namespace) -> None:
763
879
  milestone = getattr(args, "milestone", None) or _active_milestone(state)
764
880
  if milestone and milestone not in state.get("milestones", {}):
765
881
  _die("unknown_milestone")
882
+ # confirm-parent gate (OPT-IN): a task may not be detailed before its parent milestone is
883
+ # human-confirmed — but ONLY when the milestone opted in via `new-milestone --await-confirm`.
884
+ # validate-then-write — refuse BEFORE any scaffold/state mutation. A milestone with no
885
+ # `confirmed` key (non-flag + pre-existing) is grandfathered (mirrors the setup-lock).
886
+ if milestone and not _milestone_confirmed(state, milestone):
887
+ _die(f"milestone_unconfirmed: confirm it first — add.py milestone-confirm {milestone}")
766
888
  depends_on = _parse_deps(getattr(args, "depends_on", None))
767
889
 
768
890
  # SEED (--from-delta): resolve a prior task's FIRST open SPEC delta into THIS task.
@@ -796,8 +918,13 @@ def cmd_new_task(args: argparse.Namespace) -> None:
796
918
  # inherit the project's DECLARED autonomy default (task init-auto-default) — fail-SAFE:
797
919
  # absent -> auto, garbled -> conservative; the posture is project-scoped, not hardcoded.
798
920
  autonomy = _project_autonomy(root)
921
+ # fast lane (fast-new-task-flag): --fast scaffolds the MINIMAL template instead of the full one.
922
+ # The human opts in explicitly (the engine never guesses ceremony); the freeze floor is held by
923
+ # the freeze-before-build gate's fast arm (cmd_advance), so the lighter shape never drops the trust seam.
924
+ fast = bool(getattr(args, "fast", False))
799
925
  rendered = _render_template(
800
- "TASK.md", title=title, slug=slug, date=date.today().isoformat(),
926
+ "TASK.fast.md" if fast else "TASK.md",
927
+ title=title, slug=slug, date=date.today().isoformat(),
801
928
  stage=state["stage"], autonomy=autonomy)
802
929
  if feature_override: # pre-fill §1 from the seeded delta
803
930
  rendered = re.sub(r"(?m)^Feature:.*$",
@@ -822,6 +949,8 @@ def cmd_new_task(args: argparse.Namespace) -> None:
822
949
  }
823
950
  if from_delta:
824
951
  state["tasks"][slug]["from_delta"] = from_delta # lineage: seeded from <prior>
952
+ if fast:
953
+ state["tasks"][slug]["fast"] = True # durable lane marker (absent == not-fast)
825
954
  _set_active_task(state, slug, milestone)
826
955
  save_state(root, state)
827
956
  print(f"created task '{slug}' -> {task_md}")
@@ -938,7 +1067,29 @@ def cmd_advance(args: argparse.Namespace) -> None:
938
1067
  # freezes — the unmarked predecessors are never retro-redded). REFUSE writes
939
1068
  # nothing (fail-closed); below the build boundary the flag is never checked.
940
1069
  if nxt == "build":
1070
+ # the OPTED-IN crossing guards (fast-lane + flow-enforcement): a task whose PARENT
1071
+ # milestone opted into --await-confirm is held to the trust floor at tests->build. A
1072
+ # task under a plain / no milestone is never gated (every existing advance-to-build flow
1073
+ # stays green). validate-then-write — every refusal runs BEFORE the tripwire/scope
1074
+ # snapshots below, writing nothing; the task stays at `tests`.
1075
+ _ms = state["tasks"][slug].get("milestone")
1076
+ _optin = bool(_ms) and (state.get("milestones") or {}).get(_ms, {}).get("await_confirm") is True
941
1077
  raw3 = _raw_phase_bodies(root, slug).get(3, "")
1078
+ # freeze-before-build gate (fast-lane): "collapse-never-skip" made REAL — the freeze is
1079
+ # engine-enforced for an opted-in milestone OR any --fast task (the fast arm, fast-new-task-flag:
1080
+ # a fast task is held to the floor under ANY milestone, so the lighter lane never drops the
1081
+ # trust seam). PRECEDES build-expectations: you freeze §3 before pre-declaring §6's outcomes.
1082
+ _freeze_gated = _optin or state["tasks"][slug].get("fast") is True
1083
+ if _freeze_gated and not _contract_frozen(raw3):
1084
+ _die("contract_not_frozen: freeze §3 before crossing into build — approve "
1085
+ f"the contract in {slug}'s TASK.md (Status: FROZEN @ vN)")
1086
+ # build-expectations gate (flow-enforcement): an opted-in task may not enter build until
1087
+ # its §6 `### Build expectations` are pre-declared — so verify checks the build is RIGHT,
1088
+ # not just green. Same opt-in switch as the contract-fill gate, one level out.
1089
+ if _optin:
1090
+ if _section_unfilled(_raw_phase_bodies(root, slug).get(6, ""), "### Build expectations"):
1091
+ _die("build_expectations_unfilled: fill the §6 '### Build expectations' block "
1092
+ f"of {slug}'s TASK.md before crossing into build")
942
1093
  if _contract_frozen(raw3):
943
1094
  if not _flag_well_formed(raw3):
944
1095
  _die("unflagged_freeze: a frozen §3 must surface a well-formed "
@@ -1126,6 +1277,7 @@ def cmd_gate(args: argparse.Namespace) -> None:
1126
1277
  state["tasks"][slug]["gate_actor"] = _actor_stamp(state) # WHO recorded the verdict (every outcome)
1127
1278
  state["tasks"][slug]["updated"] = _now()
1128
1279
  save_state(root, state)
1280
+ _stamp_gate_record(root, state, slug, args.outcome) # mirror the verdict into §6 (Finding C)
1129
1281
  print(f"task '{slug}' gate -> {args.outcome}")
1130
1282
  # the engine-sourced next step (next-footer-engine): a completing gate hands off to the
1131
1283
  # state arm; HARD-STOP routes to "resolve HARD-STOP …" — converging the old bespoke line.
@@ -1531,7 +1683,10 @@ def cmd_status(args: argparse.Namespace) -> None:
1531
1683
  if _rel:
1532
1684
  print(f" → {RELEASABLE_CUE.format(n=len(_rel))}")
1533
1685
 
1534
- print(f"active : {active or '(none)'}")
1686
+ # fast-lane marker (fast-new-task-flag): tag an ACTIVE fast task so the lane is visible at a
1687
+ # glance. Presentation-only, existence-gated — a plain/absent active task is byte-unchanged.
1688
+ _fast_mark = " · fast" if active and tasks.get(active, {}).get("fast") is True else ""
1689
+ print(f"active : {active or '(none)'}{_fast_mark}")
1535
1690
  # parallel streams (parallel-status-view): when >=2 milestones are active, render each as its
1536
1691
  # own stream (active task + phase) so a user working N fronts reads them all at once. ADDITIVE —
1537
1692
  # the N<=1 output above is byte-identical (the standing additive-cue convention); presentation
@@ -2577,17 +2732,64 @@ def cmd_new_milestone(args: argparse.Namespace) -> None:
2577
2732
  _atomic_write(mfile, _render_template(
2578
2733
  "MILESTONE.md", title=title, goal=args.goal or "<goal>",
2579
2734
  stage=args.stage, date=date.today().isoformat()))
2580
- state["milestones"][slug] = {
2735
+ # confirm-parent gate (OPT-IN, mirrors `init --await-lock`): `--await-confirm` seeds the
2736
+ # milestone UNCONFIRMED so new-task is held until `add.py milestone-confirm`. WITHOUT the flag
2737
+ # NO `confirmed` key is written → grandfathered-confirmed → no gate (so the existing engine
2738
+ # tests stay byte-green). The guided skill flow passes the flag at the human-review point.
2739
+ await_confirm = bool(getattr(args, "await_confirm", False))
2740
+ record = {
2581
2741
  "title": title, "goal": args.goal or "", "stage": args.stage,
2582
2742
  "status": "active", "created": _now(), "updated": _now(),
2583
2743
  }
2744
+ if await_confirm:
2745
+ # `await_confirm` is the STABLE opt-in marker (set ONLY here, at creation). `confirmed`
2746
+ # alone is NOT a reliable opt-in signal: milestone-confirm stamps confirmed:true on a plain
2747
+ # milestone too, so a later build-entry gate must key on `await_confirm`, not `confirmed`.
2748
+ record.update(confirmed=False, confirmed_at=None, confirmed_by=None, await_confirm=True)
2749
+ state["milestones"][slug] = record
2584
2750
  _set_active_milestone(state, slug)
2585
2751
  save_state(root, state)
2586
2752
  print(f"created milestone '{slug}' -> {mfile}")
2587
- print("active milestone set.")
2753
+ print("active milestone set." + ("" if not await_confirm else
2754
+ " (unconfirmed — show the MILESTONE.md, then: add.py milestone-confirm " + slug + ")"))
2588
2755
  print(_next_footer(root, state)) # converges the old "Decompose it into tasks: …" hint
2589
2756
 
2590
2757
 
2758
+ def cmd_milestone_confirm(args: argparse.Namespace) -> None:
2759
+ """The human gate that opens new-task for a milestone (confirm-parent). Mirrors `cmd_lock`
2760
+ one level down: the human reviews the filled MILESTONE.md, then RECORDS confirmation here.
2761
+ The engine never self-confirms. Validate-then-write; re-confirm is an idempotent note."""
2762
+ root = _require_root()
2763
+ state = load_state(root)
2764
+ slug = args.slug
2765
+ if slug not in state.get("milestones", {}):
2766
+ _die("unknown_milestone")
2767
+ m = state["milestones"][slug]
2768
+ if m.get("confirmed") is True:
2769
+ print(f"milestone '{slug}' already confirmed (by {m.get('confirmed_by', '?')}).")
2770
+ return
2771
+ # contract-fill gate (flow-enforcement, OPTED-IN only): a milestone that opted into
2772
+ # --await-confirm (carries a `confirmed` key) may not be confirmed until its cross-task
2773
+ # `## Shared / risky contracts` section is filled — so "confirmed" MEANS the contracts
2774
+ # were present at confirm time. A grandfathered no-key milestone keeps the plain stamp
2775
+ # (gate skipped — keeps the census + existing flows green). Validate-then-write.
2776
+ if "confirmed" in m:
2777
+ mfile = root / "milestones" / slug / MILESTONE_FILE
2778
+ md = mfile.read_text(encoding="utf-8") if mfile.exists() else ""
2779
+ if _section_unfilled(md, "## Shared / risky contracts"):
2780
+ _die("milestone_contracts_unfilled: fill the '## Shared / risky contracts' "
2781
+ f"section of {slug}'s MILESTONE.md before confirming")
2782
+ who = getattr(args, "by", None) or getpass.getuser()
2783
+ m["confirmed"] = True
2784
+ m["confirmed_at"] = _now()
2785
+ m["confirmed_by"] = who
2786
+ m["actor"] = _actor_stamp(state) # structured actor alongside the free-text confirmed_by
2787
+ m["updated"] = _now()
2788
+ save_state(root, state)
2789
+ print(f"confirmed milestone '{slug}' (by {who}) — new-task is now open for it.")
2790
+ print(_next_footer(root, state))
2791
+
2792
+
2591
2793
  def cmd_ready(args: argparse.Namespace) -> None:
2592
2794
  if getattr(args, "json", False):
2593
2795
  _, state = _load_state_for_json()
@@ -5696,6 +5898,9 @@ def build_parser() -> argparse.ArgumentParser:
5696
5898
  help="with --from-delta: target the UNIQUE open SPEC delta whose text "
5697
5899
  "contains SUBSTR (case-insensitive) instead of the first")
5698
5900
  pn.add_argument("--force", action="store_true", help="overwrite TASK.md if present")
5901
+ pn.add_argument("--fast", action="store_true",
5902
+ help="opt into the fast lane: scaffold the minimal TASK.fast.md template + "
5903
+ "hold the task to the freeze floor under any milestone")
5699
5904
  pn.set_defaults(func=cmd_new_task)
5700
5905
 
5701
5906
  pdd = sub.add_parser("drop-delta",
@@ -5712,8 +5917,18 @@ def build_parser() -> argparse.ArgumentParser:
5712
5917
  pm.add_argument("--goal", default=None, help="one-sentence outcome")
5713
5918
  pm.add_argument("--stage", default="mvp", choices=STAGES)
5714
5919
  pm.add_argument("--force", action="store_true", help="overwrite MILESTONE.md if present")
5920
+ pm.add_argument("--await-confirm", action="store_true",
5921
+ help="opt into the confirm-parent gate: seed the milestone unconfirmed so "
5922
+ "new-task is held until `milestone-confirm` (mirrors `init --await-lock`); "
5923
+ "the guided skill flow passes this at the human-review point")
5715
5924
  pm.set_defaults(func=cmd_new_milestone)
5716
5925
 
5926
+ pmc = sub.add_parser("milestone-confirm",
5927
+ help="confirm a milestone (the human gate that opens new-task for it)")
5928
+ pmc.add_argument("slug")
5929
+ pmc.add_argument("--by", default=None, help="free-text confirmer name (defaults to the OS user)")
5930
+ pmc.set_defaults(func=cmd_milestone_confirm)
5931
+
5717
5932
  pr = sub.add_parser("ready", help="list tasks whose dependencies are satisfied")
5718
5933
  pr.add_argument("--json", action="store_true", help="machine-readable JSON output")
5719
5934
  pr.set_defaults(func=cmd_ready)
@@ -0,0 +1,72 @@
1
+ # TASK: {{title}}
2
+
3
+ slug: {{slug}} · created: {{date}} · stage: {{stage}}
4
+ autonomy: {{autonomy}}
5
+ phase: ground <!-- fast lane: ground -> specify -> contract -> tests -> build -> verify -> observe -> done -->
6
+ fast: true <!-- the fast lane: a small task, collapsed flow + minimal template. Omit --fast for full rigor. -->
7
+
8
+ > Fast lane — one small task, minimal sections, filled top-to-bottom. The trust floor still
9
+ > holds: a FROZEN §3 contract · ≥1 red test before build · a recorded §6 gate (security = HARD-STOP).
10
+ > The acceptance scenario collapses into §1 `Accept:`; OBSERVE is one optional line at the gate.
11
+
12
+ ---
13
+
14
+ ## 0 · GROUND — the real codebase
15
+
16
+ Touches (files · symbols): <path:symbol — what it is / how it is keyed>
17
+ Anchors the contract cites: <the symbols §3 will name>
18
+
19
+ ---
20
+
21
+ ## 1 · SPECIFY — the rules
22
+
23
+ Feature: <name>
24
+ Must:
25
+ - <required behavior>
26
+ Reject:
27
+ - <bad input / situation> -> "<error_code>"
28
+ Accept: <the one acceptance scenario, Given/When/Then condensed to one line — it drives the §4 test>
29
+ Assumptions: ⚠ <the one most likely wrong> — why; if wrong: <cost> (or "none material — biggest risk: X")
30
+
31
+ ---
32
+
33
+ ## 3 · CONTRACT — freeze the shape
34
+
35
+ ```
36
+ <the shape that freezes: signature / fields · success + each rejection's response>
37
+ ```
38
+
39
+ `Least-sure flag surfaced at freeze:` <[spec|contract|test] the point most likely wrong — why; if wrong: cost>
40
+ Status: DRAFT
41
+ <!-- The freeze IS the one approval. Approved -> Status: FROZEN @ vN — approved by <name>.
42
+ Changing a frozen contract = change request back to SPECIFY. -->
43
+
44
+ ---
45
+
46
+ ## 4 · TESTS — failing-first (red)
47
+
48
+ Plan: test_<accept> — assert the §1 Accept line's Then (behavior, not internals).
49
+ Tests live in: `./tests/` · MUST run red (missing implementation) before Build.
50
+
51
+ ---
52
+
53
+ ## 5 · BUILD — AI writes code
54
+
55
+ Scope (may touch): `./src/` <every file the build may write — declared before the §3 freeze>
56
+ Code lives in: `./src/` · Constraints: change no test, no contract; allow-list packages only.
57
+
58
+ ---
59
+
60
+ ## 6 · VERIFY — evidence + gate
61
+
62
+ - [ ] all tests pass · coverage held · no test or contract altered during build
63
+ - [ ] green was EARNED — no overfit / vacuous asserts / stubbed-away logic
64
+ - [ ] no exposed secrets, injection openings, or unexpected dependencies (security = HARD-STOP)
65
+
66
+ Build expectations (from §1 Accept + §3 CONTRACT): <the observable outcome a correct build must produce — confirmed by <how / where>>
67
+
68
+ ### GATE RECORD
69
+ Outcome: <PASS | RISK-ACCEPTED | HARD-STOP>
70
+ Reviewed by: <name> · date: <date>
71
+ <!-- A security finding is ALWAYS HARD-STOP. Record exactly one outcome — no silent pass.
72
+ OBSERVE (optional): one `[SPEC · open]` or competency-delta line here if the loop taught the foundation something. -->