Skip to content

feat(v-onboard): add Operations/Deployment coverage dimension to /v:onboard - #4

Open
khymerao wants to merge 1 commit into
procoders:mainfrom
khymerao:feat/v-onboard-operations-dimension
Open

feat(v-onboard): add Operations/Deployment coverage dimension to /v:onboard#4
khymerao wants to merge 1 commit into
procoders:mainfrom
khymerao:feat/v-onboard-operations-dimension

Conversation

@khymerao

@khymerao khymerao commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

What

Adds an explicit Operations / Deployment coverage dimension to the /v:onboard pipeline. When a repo has CI/CD, container, or deploy files, onboarding now produces a cited docs/superpowers/architecture/operations.md instead of silently dropping that layer from the generated knowledge base.

Additive & gatedoperations.md is only generated when ops files are present and the maintainer confirms it at the existing HUMAN GATE; an unattended/auto-approve run includes it with no new code path.

Why

pack already includes docker/**, .github/workflows/*, Terraform, and deploy scripts, so the raw material reached EXTRACT — but DETECT never inventoried it, EXTRACT had no claim type for it, and the arch doc set was fixed at architecture.md / business-logic.md / tech-context.md. Net effect: Docker topology, CI/CD, production domain, and runbooks were silently skipped — becoming confident partial truth downstream. This closes that blind spot.

How it works

  • New deterministic detect-ops subcommand (mirrors detect-ui) inventories three categories — CI/CD (.github/workflows/*, .gitlab-ci.yml, .circleci/config.yml, Jenkinsfile, …), containers/infra (Dockerfile*, docker-compose*/compose.*, *.tf/*.tfvars, k8s heuristics), and deploy/PaaS (Procfile, fly.toml, vercel.json, deploy*.sh, …). Returns {signals_found, ci_cd[], containers[], deploy[]}; CLI prints ops/no-signals or --json. Empty scan = open question at the gate, never a no-ops verdict.
  • EXTRACT gains a 5th claim type (operations) targeting operations.md; load-bearing rules still bite (deploy-secret path, prod/branch gate, fail-closed CI check). type is free-form to verify-citations, so no schema change.
  • The HUMAN GATE (§6) presents operations.md as its own explicit per-artifact confirm; declining drops it. k8s detection is an honest filename/dir heuristic, flagged as such.

Contents

  • scripts/compound-v-onboard.pydetect_ops() + _ops_category() + detect-ops CLI + selftest (walks the filesystem excluding VENDOR_DIRS, so the non-git --selftest trees detect too)
  • skills/compound-v/onboarding.md — DETECT bullet, EXTRACT type, operations.md doc section, §6 gate confirm, §7 write surface, refresh note
  • docs/superpowers/specs/2026-06-30-v-onboard-design.md — artifacts-table row + conditional-fourth prose
  • Design spec + implementation plan under docs/superpowers/{specs,plans}/2026-07-17-*

Conventions / gates (verified locally)

  • ✅ Rebased onto main; python3 scripts/compound-v-onboard.py --selftest — 77/77 checks pass
  • detect-ops --json verified on this repo (detects its own .github/workflows/*); no-signals on a bare tree
  • ✅ No verify-citations / claims-schema change; no new runtime deps, hooks, or scripts
  • ⚠️ scripts/lint-frontmatter.py not run — no pyyaml in the local env; N/A regardless (diff touches no frontmatter)

@copeus

copeus commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Review: the gap is real, and one change would make the fix honest

Verified the premise against the code before judging it, and it holds:

  • pack() already picks up **/Dockerfile, workflows and Terraform — the raw material does reach the pipeline
  • DETECT has exactly one function today, detect_ui. Nothing inventories ops
  • grepping all four generated architecture docs for deploy|docker|ci/cd returns nothing

So the material arrives and evaporates between stages. Your framing — "becoming confident partial truth downstream" — is the accurate one, and it's the part that matters: an agent reading our KB gets a document that looks like a complete description of the system and contains no production domain and no deployment path.

The implementation follows the existing precedent (detect_ops mirrors detect_ui), the claim type is free-form so no schema change is needed, it's gated behind the existing human confirm, and the k8s heuristic is labelled as a heuristic rather than presented as fact. That last one is exactly the house rule, and it's good to see it applied unprompted.

The one change I'd ask for

detect_ops matches a fixed list — GitLab, CircleCI, Travis, Azure, Bitbucket, Dockerfile*, *.tf, Procfile, fly.toml, vercel.json. A project that deploys via a bespoke ship.sh matches nothing, and the pipeline concludes "no ops".

That is the same bug this PR fixes, moved down one floor. Today the KB is silently missing the ops layer; after this it would silently assert there isn't one — and a stated absence reads more confidently than a gap.

Ask: don't let the detector conclude "no ops" — let it report "no signals found", and surface that at the gate as a question rather than a verdict: "no explicit ops files detected — if this project does deploy, point me at it." Then the hardcoded list works as an accelerator for the common case instead of a verdict on the uncommon one, and the human stays the backstop exactly where the heuristic is blind.

This is the same rule we applied elsewhere this week: an incomplete scan must never read as a clean one. complete: false beats a confident empty result.

(Fair disclosure: the same critique applies to the detect_ui we already shipped — three filenames and four extensions. You copied our pattern, you didn't invent a shaky one. If you'd rather, moving the signal lists into config so they can be extended without a code change would let us fix both at once, and I'd take that as a follow-up rather than a blocker here.)

Minor

The body says 69/69 selftest checks; main is now at 72, so the branch is a little behind. Needs a rebase and a re-run before merge.

Verdict

Take it. Five files, no overlap with the in-flight v2.18 branch or with #5#8, real gap, house pattern. This is the cleanest merge in the queue. Please add the "no signals found" framing and rebase, and I'll merge.

@khymerao
khymerao force-pushed the feat/v-onboard-operations-dimension branch from 5188dd1 to 47234a4 Compare August 10, 2026 21:53
@khymerao

Copy link
Copy Markdown
Contributor Author

Addressed both, thanks for the sharp read.

"no signals found", not "no ops"detect_ops now returns signals_found (was present), and its falsity is documented + surfaced as no signals found, never an absence verdict:

  • CLI prints no-signals (an open question), not no-ops.
  • GATE (§6) now handles both branches. signals_found: false is no longer a silent skip — it poses: "No explicit ops files detected — if this project deploys, point me at it (e.g. a hand-rolled ship.sh)." The human, not the heuristic, decides absence. Autonomous run records "no signals found (not confirmed absent)" so the open question survives to the next interactive pass.
  • Selftest asserts the bare-tree result carries empty lists (no false verdict), not just false.

The hardcoded list now works as the common-case accelerator you described, with the human as backstop where it's blind.

Rebase — rebased onto main; selftest is 77/77 now (body updated).

Follow-up (config-driven signal lists for both detect_ops and detect_ui) — agree it's the real fix for the shared blind spot; taking it as a separate follow-up per your note, not folding it in here.

copeus added a commit that referenced this pull request Sep 1, 2026
…oor, Workflow orchestration

Four decisions locked in brainstorm: a third SCOPED tier; a narrow
mechanically-checkable auto-route class that amends Iron Invariant #4 rather
than deleting it; the impacted-tests reversal bought with an unconditional
floor plus the CI backstop; and Workflow as the dispatch engine with Engine A
retained for the subagent/headless/epic paths that provably cannot launch one.

Corrects the recon on one point: the scope gate CAN be interposed. The script
has no shell, but the agents it spawns do, so the gate becomes a pipeline stage
rather than script logic -- which makes it mechanical for the first time on the
claude path that carried 73 of 73 real jobs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
copeus added a commit that referenced this pull request Sep 1, 2026
r1 is superseded, not patched -- spec drift by patching is a repeat failure here.

Killed by the pre-flights: the headless justification for Engine A (workflows ARE
available in -p and the SDK; only the ultracode keyword is route-restricted); the
name "Engine B" (taken by the rejected claude -p shell-out; the Workflow engine
is Engine C in nine places); a false citation of a || true contract that lives on
the v2.18 branch, not main; and AC-8, which had no schema field and no transport.

Found and closed: a Gate stage that throws would skip Record entirely and lose the
audit trail on exactly the jobs that failed (v2.6.4 recurring); agent() returning
null would read as "no violations"; override #4 returns low/low bands beside
override_fired=4 and would have granted auto-route to an overridden record; the
auto-route class gated on shape with no checks-passed and no history predicate,
and re-validated only line count post-diff, leaving three TOCTOU windows open;
docs-only is a self-modification hole here because skills/agents markdown IS the
enforcement layer; the floor was missing previously-failing and newly-added; and
none of tests/ is executed by CI at all.

v2.18-autonomy is absorbed: Feature C already exists as _enforcement_rule in its
hook, and two blocking Stop registrations are undefined behaviour.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
copeus added a commit that referenced this pull request Sep 1, 2026
…ut DIRECT

Why: v3.0 gives the scorer a third tier and a narrow class of changes that land
unattended. The old invariant — "The score only OFFERS, never auto-routes" — was
the charter every consumer read, and once the DIRECT auto-route class exists that
sentence is not a stricter policy, it is a false one. A prose rule contradicted by
the code it governs teaches agents to discount the whole file, so it is replaced
everywhere it is asserted in this lane rather than left standing beside its own
exception.

The amended wording (spec §A4) is verbatim: the score OFFERS by default; it
auto-routes only inside the DIRECT auto-route class, whose membership is decided by
mechanically checkable predicates and never by model judgement; every other tier
still requires a human offer and acceptance.

- phase-preeval.md: the canonical statement (Invariant #4), the Stan Edgar framing
  that restated it, and Gate 3's two-valued verdict enum, now three-valued.
- SKILL.md: Stage -1 rewritten to the three tiers, plus the pipeline diagram.
  Edited strictly in place — business-logic.md and tech-context.md cite this file
  by line range and are in other lanes, so the line count is unchanged.
- rationalization-table.md: the "design was already settled" row said the fast-path
  "never auto-routes"; new row refutes "the change is tiny, I'll skip the triage
  record" — the record is what buys the DIRECT branch, and it is the numerator the
  miscalibration breaker counts.
- routing-policy.md: triage decides whether anything reaches this table at all, and
  the scorecard's own "never auto-route to a lower-trust backend" is disambiguated
  from #4 rather than silently colliding with it.
- project-config.py: the fail-closed comments claimed a bad value can "never become
  an auto-route"; the accurate claim is that it can never ENLARGE the class.
- architecture.md: the citation-verified claim "it never auto-routes" was about to
  become false.
- pre-eval-config.md: stops declaring a closed two-value decision enum, and states
  the record schema's taxonomy conditional in its NEGATIVE form (decision !=
  FULL_PIPELINE). Keyed on FASTPATH_ELIGIBLE it was correct only while the enum had
  two values: with a third, a null-taxonomy record would validate as SCOPED — a real
  routing decision taken without the protections the taxonomy is the only source of.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
copeus added a commit that referenced this pull request Sep 1, 2026
…anded in 3c15b8c)

EMPTY BY NECESSITY, not by choice. This job's four files were staged when a
concurrent commit in the same working tree (3c15b8c, the r7 spec commit) ran
`git commit` and swept the index, so the diff is already in history under an
unrelated message. Nothing is amended — that commit also carries another agent's
work. This records the WHY the swept commit does not.

WHY THIS JOB EXISTS: v2.18 shipped the mechanism and left the surface unwired.
`--arm-goal`, `--disarm-goal` and `--goal-status` exist in
scripts/compound-v-epic-state.py and hooks/epic-goal-stop.sh reads them, but no
command ever CALLED them — an armed goal was reachable only by hand-running the
script. Feature F's headless shim was likewise an orphan, referenced by README and
CI and invoked by nothing. A mechanism nothing invokes is prose with a test suite,
which is the exact defect this release exists to remove.

WHAT LANDED:
- commands/v-epic.md — §0d arms the goal (offered, never silent): session id from
  the harness, refuse rather than invent one when it is empty, the two shipping
  conditions, max_continues > 0 with `0` stated as invalid, the §9 commit rule, and
  a hook-store check that can FALSIFY the arm rather than assume it took. §0e prints
  the shim artifact and installs nothing, never pre-filling --allow-build. "Goal
  disarm" pops the record at every terminal exit and takes its wording from `met`,
  never from "the hook stopped blocking".
- commands/v-status.md — an armed-goal column off the read-only --goal-status,
  degrading to an em-dash on every failure path. Continuations are always "—": that
  counter lives in the hook's own store keyed on a session id this command does not
  have, and a guessed count is the fabricated metric the charter forbids.
- commands/v-init.md — `enforcement.*` seeded OFF as a MAP of named gates, so 3.0's
  triage_gate joins 2.18's pipeline_bypass by adding a key, not by changing shape.
  /v:init never offers to turn one on: a false positive costs every teammate a
  blocked turn. Also amends Iron Invariant #4 to its v3.0 wording at the three sites
  here that still asserted the deleted absolute (coordinator hand-off from task-5).
- skills/compound-v/epic-mode.md — the armed-goal lifecycle, one armed epic per
  project (per-file refusal plus fail-open cross-file discovery), session_id
  matching, and the distinction that pays for all of it: a terminal-but-unmet epic
  yields "do not continue", not "met". Calling a stopped epic finished is a
  fabricated completion claim, and should_continue is not a completion answer.

Every flag documented was run before it was written about, and the
terminal-but-unmet JSON quoted in epic-mode.md is a real probe, not an example.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
copeus added a commit that referenced this pull request Sep 1, 2026
… from becoming a DIRECT

The band matrix collapsed 8 of its 9 cells into FULL. A medium/medium change —
the ordinary case — bought the whole pipeline, and small work escaped triage
entirely rather than being over-processed. `DECISION_SCOPED` ("SCOPED_PIPELINE")
un-collapses the middle: manifest, run dir, scope gate, floor and one combined
review, without recon or the three pre-flights.

WHY each piece is shaped the way it is:

* The matrix is a lookup INSIDE `score`, applied at Layer B on the bands as
  computed. It is not a post-hoc read of the verdict dict, and that distinction
  is the whole job. Override #4 (semantic-vs-path disagreement) returns the
  GENUINE low/low bands beside `override_fired=4` — a reader that re-derived the
  tier from the record's two band fields would hand DIRECT, and with it the
  auto-route class, to a record whose own audit trail says a hard override
  fired. The selftest asserts exactly that: the bands really are low/low, a
  post-hoc read of them really would say DIRECT, and the engine really says FULL.

* ANY FIRED OVERRIDE FORCES FULL is enforced in `_verdict`, the single
  construction point of every verdict, not repeated at each of the seven return
  sites. Today every Layer-A row already passes FULL, so it changes no behaviour;
  it exists so no future edit can produce the pairing at all. The record schema
  now machine-checks the same invariant, so a regressed producer cannot write one
  either.

* Unknown bands stay fail-closed twice over: override #6 catches them first, and
  the matrix lookup's default is FULL, so the table cannot fail open on a band a
  future taxonomy invents.

* A low/low change that fails a DIRECT predicate (fan_out, single literal path)
  demotes ONE tier, to SCOPED — not to FULL. The matrix owns the tier; Layer B
  only gates the DIRECT cell, and SCOPED is where the matrix already puts this
  cell's low/medium and medium/low neighbours. The spec does not state this case;
  it is called out in the handoff report as a resolved ambiguity.

* `fast_path: "off"` names SCOPED explicitly rather than letting it fall through
  the `== FASTPATH` test into the untouched arm. An operator who set that switch
  asked for the full pipeline; handing them a tier that also skips recon and the
  pre-flights would be the kill-switch failing open on a value that did not exist
  when the switch was written.

* `postdiff-reclassify` returns `escalate_to` beside `escalate`. A bare boolean
  was a complete answer only while "not the fast path" could mean one thing.
  DIRECT demotes to SCOPED (spec §A4 predicate 8), SCOPED to FULL, and an absent
  `from_decision` — every pre-3.0 caller — falls closed to FULL, so existing
  callers keep exactly their old meaning while a 3.0 caller gets the one-tier
  demotion. The tier strings are duplicated there as a wire vocabulary rather
  than imported, and a selftest compares them against the scorer's constants so
  the two cannot drift silently.

* The schema's taxonomy conditional is re-keyed from `decision ==
  FASTPATH_ELIGIBLE` to `decision != FULL_PIPELINE`. Keyed positively it silently
  stopped applying the moment a third tier existed, and a SCOPED record with a
  null taxonomy would have validated — the exact fail-open that check exists to
  prevent. Forged records now prove the schema refuses it in both tiers while the
  real absent-taxonomy FULL record still validates.

* Iron Invariant #4 is amended here to the §A4 wording (the score OFFERS by
  default; it auto-routes only inside the DIRECT auto-route class, decided by
  mechanically checkable predicates and never by model judgement) in this lane's
  two carriers only — the engine docstring and the record schema. The other
  copies belong to task-5.

The new value is documented in the module docstring as an interface, because
tasks 2, 3 and 4 consume it and cannot read this code.

Selftests: preeval 85 -> 131 cases, postdiff-reclassify 64 -> 78. All nine matrix
cells, seven fail-closed band pairs, both halves of the override hazard, the
`_verdict` backstop across all 7 override rows x both proportionate decisions,
and end-to-end SCOPED record write + schema validation.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
copeus added a commit that referenced this pull request Sep 1, 2026
Spec §C says the triage record binds session_id, base_commit and
declared_paths, and the Stop-hook rule task-8 landed reads exactly those to
decide whether a record covers the current diff. The schema is
additionalProperties:false and carried none of them, so no record could express
them and no record could ever cover anything. The gate is off by default, so
nothing was broken — Feature C would simply have shipped inert, a correct
mechanism with nothing to feed it.

WHY each field is shaped the way it is:

* `session_id` is optional and nullable, never required. This engine never sees
  a session; only /v:triage does. Requiring it would invalidate every record the
  scorer writes outside a triage command. Absent or null means the record binds
  no session and therefore covers nothing, which is the fail-closed direction the
  gate's exact-match test already produces. The EMPTY STRING is rejected: it
  looks like a binding and can never match, and that is the one shape that
  misleads a reader.

* `declared_paths` items are constrained to what the consumer can actually read.
  The hook serializes candidates as `tier<US>run_id<US>path` and DROPS any entry
  containing U+001F, LF or CR. Dropping narrows the declared set, which is safe
  for the gate but silent for the producer — so the schema rejects every C0
  control character here, where the producer still finds out. Absolute paths and
  `..` segments are rejected for the same reason: git never reports either, so
  such an entry could only ever match nothing while looking like coverage.

* `base_commit` is expressible and deliberately inert. task-8 reads it and
  derives no freshness rule, because HEAD legitimately advances mid-session —
  /v:triage's own commit of the record moves it — so a mismatch is not evidence
  of staleness. The description says so, and says not to add such a rule without
  evidence that a mismatch correlates with a bad decision.

* `tier` is added and PINNED to `decision` by three conditionals. The hook
  prefers `.tier` over `.decision` when present, so an unconstrained `tier` would
  be a second, higher-priority source of truth for a safety-relevant
  classification: a record could say FULL_PIPELINE and wear `tier: DIRECT` and be
  exempted as the auto-route class. Pinned, the hook's preferred branch is
  provably identical to its fallback instead of a way around it. This is the same
  hazard as override #4 one layer up, and it gets the same answer — make the
  disagreement unrepresentable rather than trusted.

* `build_record` gains an optional `binding` kwarg, and it is a footgun removal
  rather than a convenience. `digest` covers the whole record, so a producer that
  attached these fields after building would ship a record whose self-integrity
  digest silently no longer verifies — silently, because `digest` is optional and
  checked only when present. The kwarg folds the binding in before the digest is
  taken. It does NOT accept `tier`: that is derived from the decision, so the
  producer cannot introduce the disagreement the schema forbids. Without a
  binding the record is byte-for-byte and digest-for-digest what it was.

Verified against the consumer, not just asserted: the hook's own `_TRIAGE_JQ`
was extracted verbatim and run over records built to this schema. All three
decisions map to DIRECT/SCOPED/FULL, a record carrying explicit `tier` produces
output identical to its decision-only twin, and a foreign-session record emits
nothing.

Selftests: preeval 131 -> 174 cases. Covers tier agreement and disagreement for
all three decisions, session_id null/absent/empty, base_commit shapes, the three
declared-path forms the gate understands, twelve rejected path shapes including
the separator and line breaks it would otherwise drop, and the digest staying
correct through build_record while breaking when bolted on afterwards.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
copeus added a commit that referenced this pull request Sep 1, 2026
… orphan

task-1 closed with 174 selftests and made the tier/decision disagreement
UNREPRESENTABLE rather than merely discouraged: task-8 prefers a record's .tier
over its .decision, so an unconstrained tier field would have been a second,
higher-priority source of truth for a safety classification -- a record could
read FULL_PIPELINE, wear tier DIRECT, and be exempted as the auto-route class.
Three schema conditionals pin them together. That is override #4's hazard one
layer up, answered the same way.

Two rulings it asked for:

1. RATIFIED, with the rule narrowed. My "consumers read the constants from the
   engine" was too broad. A sibling analyser that imports nothing and consumes
   the value as JSON off a record may declare its own constants, PROVIDED a
   selftest loads the scorer by path and asserts equality. The hazard named was
   drift; that test closes drift directly, while a runtime import for three
   strings adds a dependency whose None fallback reintroduces the literals.

2. ASSIGNED. Spec C says a DIRECT record binds the realised diff digest and
   named no owner -- the fifth mechanism this release would have shipped with
   nothing to feed it. /v:triage owns it: it computes predicate 8's post-diff
   re-validation and is the only place the realised diff exists.

Also worth recording: it closed a footgun nobody asked about. `digest` covers the
whole record, so a producer that built a record and then attached the binding
shipped one whose self-integrity digest silently no longer verified -- silently,
because digest is optional and only checked when present. build_record now takes
the binding before the digest is taken.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@copeus

copeus commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

@khymerao — the idea is accepted and we want this in: an Operations/Deployment layer that today is silently dropped from the generated docs is a real gap. The branch cannot merge as-is because /v:onboard was rewritten twice since July (3.4.x, 3.5.0), so here is what a mergeable rebase onto main (v3.5.1) looks like:

  1. scripts/compound-v-onboard.py — the file grew rules-lint/rules-plan and a hardened read path. Add detect-ops next to detect-ui in build_parser() and the main() dispatch (the line numbers in your plan are gone; anchor on the detect-ui entries). Any file detect-ops opens must go through _open_regular / _read_bounded (open-first, O_NONBLOCK, byte cap) — no bare open() on the lint/detect path anymore.
  2. skills/compound-v/onboarding.md — restructured: Tier 1 now has a citation-containment clause, there is a §Path-scoped rules section and a --refresh leg. Re-place your DETECT / EXTRACT / GATE / WRITE / refresh hunks against the current headings rather than replaying the diff.
  3. Register operations.md in the citation manifest. Generated docs are tracked in docs/superpowers/architecture/.onboard-manifest.json; the writer path is staleness --repo . --write --docmap <file> with {"docs": {doc: [cited files]}} (no --docmap writes an EMPTY manifest). Without this, staleness never flags a stale operations.md.
  4. Citations must resolve inside the repoverify-citations now refuses absolute paths, .. and symlinks escaping the checkout; run it over every file:line in the generated operations.md on your test repo.
  5. Selftest rows for detect-ops in the script's --selftest (CI runs every scripts/*.py --selftest; the row count must grow, and one planted case should FAIL without the feature).
  6. Local gate before pushing: /usr/bin/python3 -B scripts/compound-v-onboard.py --selftest, python3 scripts/lint-frontmatter.py ., the dead-link check from .github/workflows/validate.yml, and rules-lint --repo . (it must stay clean; you do not need to add a rules area for ops unless CONVENTIONS.md states an ops convention).

The spec/plan files under docs/superpowers/ can stay as they are. If you would rather we do the rebase together, say so here and we will pick it up.

Rebase of procoders#4 onto v3.5.1 (branch had drifted two /v:onboard rewrites: 3.4.x,
3.5.0). Re-placed against current anchors; addresses review.

- scripts/compound-v-onboard.py: detect-ops next to detect-ui in build_parser()
  + main() dispatch; detect_ops/_ops_category/_OPS_* + 6 selftest rows (165 ok).
  detect_ops reads only filenames (os.walk), never file contents, so the hardened
  _open_regular/_read_bounded path does not apply — noted in the docstring.
- Reviewer's core ask (accelerator, not verdict): signals_found (not 'present');
  CLI prints 'no-signals' not 'no-ops'; GATE surfaces BOTH branches — found -> confirm,
  no-signals -> open question ('point me at your deployer, e.g. ship.sh'). An empty
  scan is never a silent skip.
- onboarding.md: re-placed DETECT / EXTRACT (5th claim type 'operations') / GATE /
  WRITE surface / operations.md doc section / INDEX docmap / intro against the 3.5.0
  restructure. operations.md registered as a docmap key for .onboard-manifest.json
  ({"docs": {operations.md: [cited]}}); citations obey the new Tier-1 containment gate.
- specs: base-spec table row + conditional-fourth prose; CHANGELOG Unreleased entry;
  commands/v-onboard.md non-negotiable procoders#7.

Gates green locally: --selftest (165 ok), lint-frontmatter, rules-lint, dead-link.
@khymerao
khymerao force-pushed the feat/v-onboard-operations-dimension branch from 47234a4 to c66b21c Compare September 5, 2026 07:25
@khymerao

khymerao commented Sep 5, 2026

Copy link
Copy Markdown
Contributor Author

Rebased onto v3.5.1 and re-placed against the current code - thanks for the detailed map, it made this quick.

  • Script - detect-ops sits next to detect-ui in build_parser() and main(). One note on point 1: detect_ops only walks filenames (os.walk), it never opens file contents, so there's no read to route through _open_regular/_read_bounded. Called that out in the docstring so it doesn't look like an oversight.
  • onboarding.md - re-placed the DETECT / EXTRACT / GATE / WRITE / refresh hunks against the new headings rather than replaying the old diff (Tier-1 containment, §Path-scoped rules, the --refresh leg are all there now).
  • Manifest - operations.md is registered as a docmap key ({"docs": {"…/operations.md": [cited]}}) via staleness --write --docmap; noted that a bare --write writes an empty manifest.
  • Citations - the operations.md prose now points at the current verify-citations containment codes (path-escapes-repo, not-a-regular-file).
  • Selftest - 6 detect-ops rows added; suite is 165 ok / OK (one planted case fails without the feature).
  • Gates - --selftest, lint-frontmatter, rules-lint, and the validate.yml dead-link check all green locally.

Also carried the review point from the previous round: the detector is an accelerator, not a verdict. signals_found (not present), CLI prints no-signals, and an empty scan becomes an open question at the gate ("point me at your deployer, e.g. ship.sh"), never a silent "no ops".

Heads-up: the ~80-line script insert and the DETECT block shift a few file:line citations in architecture.md/tech-context.md/.claude/rules/docs.md. Ranges stay in-bounds so rules-lint is clean, but staleness will report cited-changed for those. Happy to re-cite in this PR, or leave it for the next /v:onboard --refresh - your call. Spec/plan under docs/superpowers/ left as-is per your note.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants