okf-kit 0.10.0 → 0.12.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/CHANGELOG.md CHANGED
@@ -7,6 +7,155 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.12.0] - 2026-09-13
11
+
12
+ ### Added
13
+
14
+ - Adds a pinned fixture-repository regression test for the documented
15
+ two-branch squash-merge decision procedure, including every stale branch
16
+ state's re-verify-and-re-stamp rescue.
17
+
18
+ ### Fixed
19
+
20
+ - `sources-fresh` under a `--repo-root` naming a SUBDIRECTORY of the
21
+ repository: both re-stamp rescues read the doc's committed value with
22
+ `git show <rev>:<path>`, and git resolves that path against the
23
+ repository's top level, never the cwd, while the rule's own doc path is
24
+ relative to the passed root. Spelled bare, the read failed (every rescue
25
+ collapsed to a `staleness not assessable` notice: a locally re-stamped
26
+ doc, or a doc re-stamped in the same commit as its source, no longer read
27
+ clean) or, with a same-named doc at the top level, read THAT doc's stamp
28
+ in the subdirectory doc's place (a dirty-but-not-re-stamped doc could
29
+ read clean, a false green). The same frame mismatch made the committed
30
+ rescue's rename-aware `git diff-tree` lookup never match the doc, so a
31
+ doc created in the co-commit read `not assessable` instead of clean. All
32
+ three sites now go through one helper that respells the path in the
33
+ top-level frame via `git rev-parse --show-prefix` (read at most once per
34
+ run and shared with `--dirty-as-now`'s dirty-path matching, which
35
+ already used it), so a subdirectory root now judges exactly as the top
36
+ level does. Cost with the flag off: one extra `git rev-parse` per run on
37
+ the re-stamp path, none per doc; a top-level `--repo-root` is otherwise
38
+ byte-identical to before. The README's "prefer the top level" limitation
39
+ paragraph is gone. (task agent-dx 266d64d3)
40
+ - `--dirty-as-now`'s shared virtual-commit instant is now pinned: the clock
41
+ is read exactly once per `check` run through an injectable `ctx.now`
42
+ (default `Date.now`), and a test asserts both the read count and that
43
+ every dirty path in both rules cites the identical instant, so a
44
+ per-call re-read can no longer slip in unnoticed. Also adds an
45
+ ignored-only new-directory control for the `--untracked-files=all`
46
+ status read, and trims `src/rules/sources-fresh.ts`'s comments to the
47
+ invariants and their reasons.
48
+
49
+ ## [0.11.0] - 2026-09-12
50
+
51
+ ### Added
52
+
53
+ - `check` gets `--dirty-as-now`: an opt-in flag under which every
54
+ uncommitted change (modified, staged, or untracked per `git status
55
+ --porcelain --untracked-files=all`) is modeled as though it landed in
56
+ ONE virtual commit made
57
+ right now, applied at a single shared choke point
58
+ (`commitEpochWithDirtyAsNow` in `src/rules/sources-fresh.ts`) so both
59
+ `sources-fresh` (a dirty `sources` path's epoch) AND `sources-fresh-future`
60
+ (a dirty DOC's own epoch) read the identical virtual instant, rather than
61
+ each rule -- or each path kind -- getting its own patch. Closes a gap
62
+ surfaced by the 2026-09-11 review-method dogfood (agent-dx PR #236): a
63
+ test file four OKF docs list as a source was edited, a pre-commit `check`
64
+ run reported 0 warnings because the source's last commit still predated
65
+ every doc's `timestamp`, and CI's strict freshness guard then failed once
66
+ the commit landed and gave the source a new, later commit time. Omit the
67
+ flag and both rules are unchanged; see the README's "Uncommitted edits
68
+ (`--dirty-as-now`)" section for the recommended pre-commit invocation and
69
+ the exact parity matrix (six working-tree states, each reproduced
70
+ through the built CLI with `--strict` exit codes, including the one
71
+ shape inherent to the model where a clean local run does NOT imply a
72
+ clean CI run: a source edit committed alone while its doc re-stamp stays
73
+ uncommitted). Carries a working-tree
74
+ analogue of `sources-fresh`'s own co-commit re-stamp rescue, evaluated
75
+ against that same virtual commit: a doc re-stamped locally (uncommitted,
76
+ on-disk `timestamp` differing -- in EITHER direction -- from the value
77
+ committed at `HEAD`; an untracked doc counts as re-stamped too) rescues a
78
+ dirty source exactly like a real re-stamp landing in the same commit
79
+ does, so the README's recommended `--dirty-as-now --strict` pre-commit
80
+ gate can actually be made green by following its own remedy (re-stamp,
81
+ then commit) while the source stays uncommitted. Reads the work tree's
82
+ dirty paths once per `check` run (`git --no-optional-locks status
83
+ --porcelain=v2 -z --untracked-files=all`), not once per unique source
84
+ path, and normalizes a `./`-prefixed or `/`-suffixed path spelling
85
+ before matching it against that dirty-paths set, so any spelling of the
86
+ same source or doc path matches consistently. `--untracked-files=all` is
87
+ load-bearing, not decoration: git's default untracked mode collapses a
88
+ brand-new directory into a single `? newdir/` record and never names the
89
+ files inside it, so a source or doc inside one matched nothing and the
90
+ flag reported `untracked by git` (exit 0) for content a plain `check`
91
+ reported STALE (exit 1) the moment it was committed -- the exact
92
+ divergence the flag exists to close. A `sources` entry spelled `.`/`./`
93
+ (the repo root) is matched by containment rather than by lookup, since
94
+ `git status` never reports the root as an entry; a `--repo-root` naming
95
+ a SUBDIRECTORY of the repository is handled by rebasing the queried path
96
+ onto the repository top level (`git rev-parse --show-prefix`, the run's
97
+ second and last extra git process) instead of silently matching nothing.
98
+ When git cannot be read at all (`status` failing, its output past the
99
+ 16 MiB `MAX_GIT_OUTPUT_BYTES` cap, git missing), the run emits one
100
+ bundle-level notice -- "`--dirty-as-now` not applied: git status could
101
+ not be read, judging by committed history only" -- rather than falling
102
+ back to committed history in a silence indistinguishable from a clean
103
+ tree. Distinguishes a genuinely untracked doc (no entry
104
+ for it at `HEAD` at all) from a real git failure reading an existing
105
+ `HEAD` blob when deciding whether a dirty doc's own re-stamp counts,
106
+ falling through to the ordinary not-assessable notice in the latter
107
+ case rather than guessing.
108
+
109
+ ### Documented
110
+
111
+ - Decision (task agent-dx 27c4f709): the anchored, path-less continuation
112
+ form the `orchestrator-workflow` package's dogfood bundle writes
113
+ (`` `:N-M#"anchor"` ``) is declared bundle-local, not ported into
114
+ `CONT_COLON_RE`/`SHORT_FORM_COLON_RE`. See the README's "Citation
115
+ resolution (citations-resolve)" section for the who-pays and the port
116
+ trigger.
117
+ - Decision, kept option (a): the rule stays as-is; two loosening
118
+ alternatives were considered and rejected. The README's "Staleness
119
+ (sources-fresh)" section now documents the two-branch squash-merge
120
+ interaction as the rule's own three-step decision procedure (the
121
+ doc-last-commit lookup under git's default history simplification, the
122
+ epoch gate, and the value-level re-stamp check) instead of an
123
+ enumeration of merge shapes, plus the one consequence that follows for
124
+ any branch merging a squash-carrying trunk. The two rejected
125
+ alternatives: (b) a source counts fresh whenever the doc's last commit
126
+ merely postdates it, with no re-stamp required -- readers pay, because
127
+ any merge or unrelated doc touch would silence real drift; (c) compare
128
+ against the doc's own last commit rather than the frontmatter stamp when
129
+ they disagree -- readers pay, because the stamp stops being the
130
+ verification claim. Under the kept option (a), branch authors pay
131
+ instead: one re-verify-and-re-stamp commit after merging a
132
+ squash-carrying trunk (or after changing a source past a trunk squash
133
+ the doc's history resolved through), the recipe now in the README.
134
+ Reproduced with a fixture repo (the trunk and eight branch variants:
135
+ doc untouched, B1; doc untouched with a second source changed after the
136
+ squash, B3; own-older-stamp kept wholesale before the squash, B2, and
137
+ after it, B2late, discriminating the epoch gate from the re-stamp
138
+ check; own-older-stamp with a mixed conflict resolution that changes
139
+ the stamp value, B2a, or does not, B2b; and both re-verify-and-re-stamp
140
+ recipe follow-ups, B2r and B3r) before writing the decision text, then
141
+ checked the procedure's own prediction against all eight plus the
142
+ trunk; no rule change. No fleet pin bump needed (no rule/behavior
143
+ change).
144
+ - Concrete case that motivated this decision: in the `agent-grounding`
145
+ repo, `docs/okf/grounding-stack-overview.md` read STALE after merging
146
+ master (batch-42 task branch `f31ad37f`'s squash-merged re-stamp) into a
147
+ second, longer-lived task branch (`d341afd5`), which carried its own
148
+ older stamp of the same doc; the batch-42 orchestrator run (pandora
149
+ workspace) recorded the resolution as decision D-015, exactly the
150
+ re-verify-and-re-stamp commit this README section now generalizes as
151
+ the recipe.
152
+
153
+ ### Security
154
+
155
+ - devDependencies: vitest and @vitest/mocker 4.1.6 -> 4.1.11
156
+ (GHSA-82fw-gwwq-j7x9), lockfile only, no runtime dependency changed
157
+ (CVE sweep 2026-09-11, agent-dx PR #235).
158
+
10
159
  ## [0.10.0] - 2026-09-06
11
160
 
12
161
  ### Added
package/README.md CHANGED
@@ -34,6 +34,10 @@ okf-kit check path/to/bundle --strict
34
34
 
35
35
  # narrow sources-fresh-future's default 10-minute clock-skew allowance
36
36
  okf-kit check path/to/bundle --future-skew-minutes 2
37
+
38
+ # pre-commit: treat an uncommitted source edit as happening now, so this run
39
+ # reports the same staleness CI will report once you commit
40
+ okf-kit check path/to/bundle --dirty-as-now
37
41
  ```
38
42
 
39
43
  ## Scaffold a bundle (`init`)
@@ -72,7 +76,7 @@ Every template doc except `benchmark-template.md` ships with `sources: [path/to/
72
76
  | `links-resolve` | error | Markdown links to other `.md` files in the bundle must resolve to a real file. Relative targets resolve against the containing file's directory; targets starting with `/` resolve against the bundle root. A relative target that climbs out of the bundle directory (`../outside.md`) and still resolves on disk is accepted; the rule checks resolution, not containment. |
73
77
  | `no-absolute-links` | warning | Link targets should not start with `/`. GitHub resolves a leading slash against the repository root, not the bundle root, so an absolute link 404s once the bundle is viewed outside its own repository. Use a same-directory relative link instead. |
74
78
  | `sources-shape` | error | Frontmatter `sources`, when present, must be a non-empty array of non-empty strings. With a repo root (explicit or auto-detected), each listed path (file or directory) must also exist under it. |
75
- | `sources-fresh` | warning / notice | For docs with a `sources` list and a repo root, flags a source path whose last git commit is newer than both the doc's `timestamp` and the doc file's own last re-stamping commit (re-stamp decided by comparing frontmatter `timestamp` values across that commit's first-parent boundary). See "Staleness (sources-fresh)" below. |
79
+ | `sources-fresh` | warning / notice | For docs with a `sources` list and a repo root, flags a source path whose last git commit is newer than both the doc's `timestamp` and the doc file's own last re-stamping commit (re-stamp decided by comparing frontmatter `timestamp` values across that commit's first-parent boundary). With `--dirty-as-now`, a source with an uncommitted change is judged as of right now instead of its last commit. See "Staleness (sources-fresh)" below. |
76
80
  | `sources-fresh-future` | warning | For the same docs as `sources-fresh`, flags a `timestamp` later than the doc file's own last commit by more than a clock-skew allowance (default 10 minutes, `--future-skew-minutes`). Catches a local wall-clock time mistakenly written with a `Z`/UTC suffix. See "Staleness (sources-fresh)" below. |
77
81
  | `citations-resolve` | warning / notice | For docs with a repo root, flags a `` `path:N`/`path:N-M` `` citation (and its `` `:N` ``/`` -`M` ``/`` (`N`) `` continuations, and bare paragraph-bound short forms `:N-M`/`(N-M)`) whose target file is missing, whose range is inverted or exceeds the file, or whose start line is blank or (for a non-markdown target) only a closing brace. A full citation may also carry an optional `#anchor` (e.g. `` `CHANGELOG.md:50-144#0.24.0` ``), checked against the target's own structure/content instead of just its line numbers. A backtick-delimited `` `path:#heading` `` citation (`.md` targets only) resolves to a whole Markdown section instead of a line range, immune to every line-number shift above it; see "Heading-section citations" below. A short-form citation's range into a test file is also checked for a describe/it block boundary. `--require-anchors` opts into five additional checks; see "Anchor strictness (opt-in, `--require-anchors`)" below. See "Citation resolution (citations-resolve)" below. |
78
82
  | `prose-line-references` | (opt-in, `--prose-line-references`) warning / notice | Off by default. Flags a prose-embedded line reference outside `citations-resolve`'s own backtick grammar (`line N`, `lines N-M`, `lines N to M`) that is drifted, unresolvable, or ambiguous once bound to the nearest named file. `--prose-line-references-strict` additionally flags every such reference as a formatting policy violation. See "Prose line references (opt-in, `--prose-line-references`)" below. |
@@ -107,8 +111,60 @@ Known limitation: a `git log` call that fails for a reason other than "no histor
107
111
 
108
112
  Known limitation: the doc-commit comparison suppresses staleness for every source older than the doc file's last re-stamping commit, not only for sources from the same commit -- provided that commit is the one that rewrote the stamp. For a multi-source doc that means a re-stamp silences drift on every source changed before it, even ones that specific re-stamp did not itself verify. The frontmatter `timestamp` still governs sources changed after the doc's last commit. Also note: the check only looks at WHETHER the `timestamp` value changed, never at whether the new value is itself correct -- a hand-typed or deliberately backdated stamp still counts as a re-stamp (`sources-fresh-future` is the rule that catches an implausible value). "Changed" means the parsed frontmatter VALUE changed: a rewrite to the same instant in another representation (`...00Z` to `...00.000Z`) still counts as a re-stamp, since the raw string differs even though it names the same instant, while adding or removing quotes around an otherwise-unchanged value does NOT count, since YAML parsing already normalizes those away before the comparison ever sees them. And the comparison is against the FIRST parent only: a merge that takes the doc wholesale from its second parent is judged against the first-parent baseline, which is the baseline the pull request under review is measured against anyway.
109
113
 
114
+ **Two-branch squash interaction:** the rule follows one decision procedure, never a catalog of merge shapes, and the procedure is what to read a squash-merge scenario against, not a list of named cases:
115
+
116
+ 1. **The doc's last commit.** `git log -1 -- <doc>` under git's default history simplification: a merge whose result for the doc path is identical to one of its parents is skipped, and the walk continues on that side to the real content-changing commit instead; only a merge that changed the doc against every parent is itself returned. The epoch lookup and the re-stamp check below both start from this same resolved commit.
117
+ 2. **The epoch gate.** A source whose own last commit is at or before the resolved doc commit is judged by step 3; a source committed after the resolved doc commit skips step 3 and is judged by the frontmatter `timestamp` alone: STALE whenever the source change also postdates that stamp (the normal case, since nothing the doc showed as of its last commit could reflect a change that had not happened yet), fresh in the rare case that the stamp itself is later than the source change, because the frontmatter comparison is the first thing the rule evaluates.
118
+ 3. **The re-stamp check (`restampedByOwnLastCommit`).** Compare the resolved commit's parsed frontmatter `timestamp` VALUE against its value in that SAME commit's first parent (rename-aware); a difference, or the doc being created there, counts as re-stamped.
119
+
120
+ One sentence for the outcome: a source reads fresh when its own last commit is at or before the doc's resolved last commit and that commit re-stamped the value, or when the source's own last commit is at or before the frontmatter `timestamp`; every other assessable case is STALE (the notice outcomes in the table above are neither fresh nor STALE).
121
+
122
+ The one consequence that follows for a branch merging a trunk carrying a squash: the squash re-dates every source it touched to the merge instant, and steps 1-3 can only ever see the MERGING branch's own resolved doc commit, never the trunk's route to that squash. So after such a merge, a clean verdict is not evidence the branch's doc was verified against the re-dated source (the known limitation above already says a re-stamp silences drift on sources it never itself checked, and step 3 only asks whether the value moved, never what it verified); a STALE verdict means the branch's resolved doc commit is older than the re-dated source, or newer but carrying no re-stamp of its own. Which of the two the rule reports depends only on where the branch's resolved doc commit falls relative to the squash (steps 1 and 2), never on who edited what:
123
+
124
+ | Branch shape | Doc commit the lookup resolves to | Verdict | Why |
125
+ |---|---|---|---|
126
+ | B never touched the doc | The trunk's squash commit (the merge is skipped: its doc content matches the trunk's) | Fresh for a source at/before the squash; STALE for a source B itself changes after the squash and after the stamp | The squash's own re-stamp (step 3) only covers sources the epoch gate lets reach it (step 2); a later change on B's own branch fails that gate |
127
+ | B carries its own older doc edit and keeps it through the merge | B's own doc-edit commit (the merge is skipped: its doc content matches B's pre-merge copy) | STALE when the edit predates the squash; fresh when it postdates the squash and changed the stamp value (the re-stamp exception); STALE when it postdates the squash but left the stamp alone | Step 3 only asks whether B's edit changed the value against its OWN first parent, never what the edit verified; an edit that lands after the squash and differs from its own prior value passes the gate and reads re-stamped regardless |
128
+
129
+ **Recipe:** after merging a trunk that carries a squash into any branch whose bundle declares the sources that squash re-dated, re-verify each such doc against its current `sources` and bump its `timestamp` in one dedicated commit, whatever `sources-fresh` currently reports for it -- the same recipe as the Authoring guidance below, just triggered by the merge. Seen in practice on a long-lived second branch after a squash-merged re-stamp landed on the trunk; see the CHANGELOG for the concrete case.
130
+
110
131
  **Authoring guidance:** when you re-verify a doc against its sources, bump its frontmatter `timestamp` (and add a line to the bundle's `log.md`) so `sources-fresh` reflects that the doc is current again.
111
132
 
133
+ ### Uncommitted edits (`--dirty-as-now`)
134
+
135
+ `sources-fresh` and `sources-fresh-future` normally judge every path by its last GIT COMMIT time, so a source or doc you have edited but not yet committed still reads by whatever it looked like at its last commit -- often still "fresh" relative to an already-stale doc `timestamp`. Once you commit, that same path gets a brand-new commit time, and CI's freshness guard (typically run in strict mode) can fail on a doc your local, pre-commit `check` run reported as clean. `--dirty-as-now` closes that gap by modeling every uncommitted change -- modified, staged, or untracked, i.e. anything `git status --porcelain --untracked-files=all` reports -- as though it landed in ONE VIRTUAL COMMIT made right now: a dirty `sources` path's epoch becomes now, and a dirty DOC's OWN epoch becomes now too, both read from the exact same shared instant so two dirty paths in one `check` run never disagree about what "now" was. Both rules read a dirty path's epoch this same way -- there is exactly one place in the code that applies the substitution, not a separate patch per rule -- so a pre-commit run reports the same verdict CI will report once the commit lands, for either rule. It has no effect on a path with no uncommitted change (judged exactly as before), and it is entirely opt-in: omit it and both rules are byte-for-byte unchanged. One consequence worth calling out: an untracked source's ordinary `untracked by git, staleness unknown` notice (see the table above) becomes a STALE warning under this flag instead, since an untracked path is itself an uncommitted change; symmetrically, an untracked DOC -- which `sources-fresh-future` otherwise leaves unassessed entirely (see that rule's table above) -- gets a real `sources-fresh-future` verdict too, since its virtual epoch is now a real "now" to compare its `timestamp` against. Both hold for a file inside a BRAND-NEW untracked directory as well, which is why the status read passes `--untracked-files=all`: git's default untracked mode reports a new directory as one single `? newdir/` record and never names the files in it, so a source or doc inside one used to be invisible to this flag (reported merely `untracked by git`, exit 0) right up until the commit that made it STALE in CI. The cost of `--untracked-files=all` is a longer status output in a work tree carrying many untracked, non-ignored files; if it ever exceeds the internal 16 MiB git-output cap, the run reports the not-applied notice below rather than pretending the tree is clean.
136
+
137
+ Three smaller behaviors worth knowing. A `sources` entry spelled `.` or `./` (the repo root itself) is dirty whenever ANY path in the work tree is dirty -- it is matched by containment, since `git status` never reports the root as an entry of its own. `--repo-root` may name a SUBDIRECTORY of the repository: `git status` reports dirty paths relative to the repository's top level, and `git show <rev>:<path>` reads a blob by a top-level-relative path too, so the rule respells every path it hands to either (via `git rev-parse --show-prefix`, read once per run) before matching a dirty path or reading a doc's committed value; dirty-path detection and both re-stamp rescues (the working-tree one here and the committed co-commit one above) then behave under a subdirectory root exactly as under the top level, and a same-named doc at the top level is never read in the subdirectory doc's place. And if git cannot be read at all (`status` failing, output past the cap, git missing), `check` emits one bundle-level `sources-fresh` notice reading "`--dirty-as-now` not applied: git status could not be read, judging by committed history only", and both rules fall back to committed history; the flag never reports a silent all-clear it did not actually verify.
138
+
139
+ The flag also carries a working-tree analogue of the doc-commit re-stamp rescue described above, evaluated against that same virtual commit: a doc re-stamped LOCALLY (its on-disk frontmatter `timestamp` value differs from the value committed at `HEAD` -- a change in EITHER direction, including a backwards move, counts -- and the doc file itself is dirty; an untracked doc counts as re-stamped too, the working-tree equivalent of "the doc being created there") rescues a dirty source exactly like a real re-stamp landing in the same commit does. Without this, bumping a doc's `timestamp` locally and re-running `check --dirty-as-now` could still report STALE, purely because a few seconds pass between writing the new timestamp and running the check -- defeating the point of the recommended invocation below. A doc that is dirty for some OTHER reason (a body edit that leaves the `timestamp` value unchanged) is not rescued: that case still reports STALE, same as before. Reading the doc's committed value distinguishes a genuinely untracked doc (no entry for it at `HEAD` at all -- always a re-stamp) from a real git failure reading an EXISTING `HEAD` blob (a corrupt object, an unreadable blob): only the former is treated as "created"; the latter falls through to the ordinary `staleness not assessable` notice instead of being silently guessed either way.
140
+
141
+ **Recommended pre-commit invocation**, matching the "re-verify, bump `timestamp`, then commit" discipline above -- run this on the working tree BEFORE committing an edit to any file a bundle lists as a `sources` entry, so a doc that needs re-stamping is caught before the commit that would otherwise make it stale in CI:
142
+
143
+ ```bash
144
+ okf-kit check path/to/bundle --dirty-as-now --strict
145
+ ```
146
+
147
+ If it reports a doc STALE, re-verify that doc against its current `sources` and bump its `timestamp` in the same commit as the source edit (or its own dedicated commit), THEN commit -- exactly the measure-after-commit discipline described above, just checked before the commit exists rather than after CI runs.
148
+
149
+ **What this actually produces for the recipe above, reproduced through the built CLI (source committed, doc committed with a matching `timestamp`, then):**
150
+
151
+ | Working-tree state | `check --dirty-as-now --strict` |
152
+ |---|---|
153
+ | Source edited, doc re-stamped on disk to now (a few seconds to a minute after writing the stamp) | exit 0, clean -- neither `sources-fresh` nor `sources-fresh-future` fires |
154
+ | Source edited, doc left alone (not re-stamped) | exit 1, `sources-fresh` STALE |
155
+ | Source edit and doc re-stamp committed TOGETHER, flag OFF | exit 0, clean (the committed-history co-commit rescue, unaffected by this flag) |
156
+ | Doc dirty for an unrelated reason (a body edit, `timestamp` value unchanged), source edited | exit 1, `sources-fresh` STALE -- not rescued |
157
+ | Doc re-stamped on disk to a value BEFORE the one committed at `HEAD` (a backwards move), source edited | exit 0, clean -- still counts as a re-stamp (see above) |
158
+ | Source edit committed ALONE, the doc's re-stamp left uncommitted in the working tree | exit 0, clean locally -- but CI is RED (`sources-fresh` STALE) until that re-stamp is committed too |
159
+
160
+ That last row is the one shape where a clean local run does NOT imply a clean CI run, and it is inherent to the model rather than a gap to be fixed: the flag judges the content in YOUR WORKING TREE, while CI judges only what you actually pushed. Commit the source edit without its doc re-stamp and CI sees a source newer than the doc's committed `timestamp`, exactly as it should. The remedy is the one this section already recommends: commit the re-stamp, in the same commit as the source edit or its own.
161
+
162
+ What "matches CI" actually means here: `--dirty-as-now` makes a local run compute the same `sources-fresh`/`sources-fresh-future` VERDICT (fresh, STALE, or not-assessable) that a post-commit `check` run would compute for the same content -- it says nothing about whether a given CI setup turns that verdict into a failed build, and nothing about warning classes outside these two rules. Whether it does depends entirely on how that CI wires up `okf-kit check`: this package's own CI runs it two different ways for the same bundle -- `.github/workflows/okf-staleness.yml` calls `check` warn-only (findings surface in the job summary and as annotations, but the job never fails on them), while `.github/workflows/ci.yml`'s freshness guard step runs `check --require-anchors` and then filters the JSON report through `jq` for `sources-fresh`/`sources-fresh-future` findings, only failing the build on THOSE. `--strict` is broader than either: it fails on every warning-severity finding from EVERY rule (including ones this flag says nothing about, like `citations-resolve`), so a locally-strict, CI-clean bundle is not a contradiction -- it means the CI in question does not block on every warning class `--strict` does. Read the target repo's actual CI wiring before treating `--dirty-as-now --strict` as equivalent to "will this pass CI".
163
+
164
+ `--dirty-as-now` ships in the same `okf-kit` release as everything else in this CHANGELOG entry; a CI step that installs a PINNED `okf-kit` version (as both workflows above do) only gains the flag once that pin is bumped to a release that includes it.
165
+
166
+ **Known limitations, stated rather than hidden.** Ignored files (`.gitignore`) are deliberately NOT treated as dirty here (`git status` is run without `--ignored`): an ignored source keeps its ordinary `untracked by git, staleness unknown` notice rather than becoming newly dirty under this flag. A source living inside a dirty SUBMODULE is likewise not detected as dirty: `git status` reports a submodule with uncommitted changes as one single record for the submodule's own path, not per file inside it, and this flag's path-matching does not currently descend into that report to find the individual dirty files a `sources` entry might name.
167
+
112
168
  ### Future-dated timestamps (`sources-fresh-future`)
113
169
 
114
170
  `sources-fresh` catches a `timestamp` that is too OLD relative to a source's last commit. `sources-fresh-future` catches the opposite mistake: a `timestamp` that is too NEW relative to the doc file's OWN last commit -- in practice, almost always a local wall-clock time hand-written with a trailing `Z` (or another UTC marker) it does not actually have, rather than a genuine future date. It never looks at `sources` commit times at all, only at the doc file's own git history, so it has nothing to say about whether any source is stale; the two rules are complementary, not overlapping, and are assessed over the same population of docs (a validly-shaped `sources` list and a repo root available, exactly like `sources-fresh` above).
@@ -118,7 +174,7 @@ Known limitation: the doc-commit comparison suppresses staleness for every sourc
118
174
  | The doc's `timestamp` is later than the doc file's own last commit by more than the skew allowance | warning | `FUTURE-DATED: doc timestamp <iso> is after the doc's own last commit <iso> (skew allowance <n>s)` |
119
175
  | The doc's `timestamp` is at or within the skew allowance of the doc file's own last commit | (nothing) | fresh: an ordinary write-then-commit gap, not a mistake |
120
176
  | The doc's `timestamp` string has no `Z`/UTC designator or numeric offset (e.g. `2026-01-01T00:00:00`, no offset) | notice | future-dated check skipped: timestamp has no UTC designator (`Z`) or numeric offset, can't be compared reliably across timezones |
121
- | The doc has no git history yet (uncommitted) | (nothing) | unknown, not flagged: there is no real commit time to compare against |
177
+ | The doc has no git history yet (uncommitted) | (nothing) | unknown, not flagged: there is no real commit time to compare against -- UNLESS `--dirty-as-now` is passed, in which case a dirty (including untracked) doc gets a real verdict against the shared virtual-commit "now" instant instead (see "Uncommitted edits" below) |
122
178
  | The doc's `timestamp` is missing or not a parseable date | (nothing) | left to `sources-fresh`'s own notice, not duplicated here |
123
179
  | No repo root available (see auto-detection above) | (nothing) | left to `sources-fresh`'s single bundle-level notice, not duplicated here |
124
180
 
@@ -180,6 +236,8 @@ Anchors are checked only once the base checks above (blank-start-line, closing-b
180
236
 
181
237
  **Continuation citations.** Once a sentence states a full `path:N` citation, prose commonly repeats just the line (or range) for a later reference in the same sentence: `` `:N` ``/`` `:N-M` `` (bare colon-prefixed), `` -`M` ``/`` –`M` `` (hyphen- or en-dash-led, the tail of a split range like `` `path:N`-`M` ``), or `` (`N`) `` (parenthesized). Each resolves against the nearest preceding citation in the same doc that resolved to a real file; a continuation right after an unresolved, ambiguous, or out-of-scope citation is silently skipped, not misattributed to an earlier, unrelated file. A heading-section citation (see above) carries no line number and is never a continuation's target: it never sets or clears which citation a later continuation resolves against.
182
238
 
239
+ **Anchored path-less continuation form: not recognised (bundle-local).** The `orchestrator-workflow` package's own dogfood bundle writes a backtick-wrapped, path-less continuation with a content anchor, e.g. `` `:913-923#"..."` ``, distinct from the two forms above. `CONT_COLON_RE` needs its closing backtick immediately after the digit range, so a trailing `#"anchor"` before the backtick never matches it; `SHORT_FORM_COLON_RE` excludes any match immediately preceded by a backtick and otherwise requires a serial-connective prefix, neither of which this form carries. So this rule sees the form as nothing at all, not merely as unresolved. Recognising it (an opt-in rule, or folding it into this one) would move the cost to okf-kit's maintainers: the rule itself, an allowlist/anchor-geometry contract as public API, and a fleet-wide pin bump on every fix. Declaring it bundle-local instead leaves the cost where it already sits: only the `orchestrator-workflow` package's own `test/docs-consistency.test.ts` guard checks the form, and every other fleet bundle that might someday write it stays unguarded until it grows the same check by hand. Port trigger, restated from the orchestrator-workflow CHANGELOG's "Citation-sibling-drift guard, okf-kit-porting decision" entry: a second fleet bundle observed carrying real sibling-citation drift in a review pass (not merely plausible in the abstract) reopens the port decision. A fleet count at the time of this decision found the live form only in that one bundle (`docs/okf/model-preselection.md`, 4 occurrences); other apparent hits were historical prose in a `log.md` narrating a re-point, not live citations.
240
+
183
241
  **Short-form citations.** Distinct from a continuation above: a *bare* (no backtick, no file name at all) colon-range `:N-M`, e.g. `:580-588` in running prose. A short-form citation binds to **the last full `path:N-M` citation named earlier in the same paragraph** -- not the nearest preceding citation anywhere in the doc (that is what a continuation does); a paragraph boundary is a blank (empty or whitespace-only) line. A short-form citation with no full citation earlier in its own paragraph is reported `short-form-unbound`, not silently skipped. Only a range is recognised, never a bare single number (`:5`): a bare number is too easily an unrelated enumeration marker (e.g. a numbered list item) to detect mechanically without a large false-positive cost. A resolved short-form citation gets every check a full citation gets, plus the test-file/Markdown block-boundary check above (`test-range-*` / `markdown-range-boundary-bracket-or-fence`).
184
242
 
185
243
  **Serial-connective gate, and why the paren form is not collected at all.** Three earlier rounds each tried to separate a real short-form citation from ordinary prose that merely contains an N-M-shaped number pair ("the window (2026-2027)", "follow steps (2-4)", "three engineers (1-3)") by deciding from the range's *values*: an inverted-pair check, a span cap plus a year/well-known-port plausibility gate, then a containment-or-adjacency check against the paragraph's last full citation. All three were eventually defeated by the same class of false positive, because every real paren-form citation this rule was ever built against has the identical shape `<English word> (N-M)` as ordinary prose -- there is no lexical signal that tells them apart. The bare parenthesized form `(N-M)` is therefore not collected as a short-form citation candidate at all; measured against this repo's own dogfood bundle, dropping it changed nothing (39 findings / 17 warnings / 22 notices, identical with and without it).
@@ -262,7 +320,7 @@ This is advisory: don't fail the build on warnings unless you pass `--strict`. U
262
320
  with:
263
321
  fetch-depth: 0
264
322
  - name: OKF bundle check
265
- run: npx okf-kit@0.10.0 check path/to/bundle
323
+ run: npx okf-kit@0.12.0 check path/to/bundle
266
324
  ```
267
325
 
268
326
  Pin the version: an unpinned `npx okf-kit` picks up new rules on their release day, which turns an unrelated PR red.
package/dist/cli.d.ts CHANGED
@@ -40,6 +40,18 @@ export interface CheckOptions {
40
40
  * `ctx.freshnessFutureSkewSeconds`.
41
41
  */
42
42
  futureSkewMinutes?: number;
43
+ /**
44
+ * Opt-in for `sources-fresh` AND `sources-fresh-future`: model every
45
+ * uncommitted change (modified, staged, or untracked) as though it
46
+ * landed in ONE virtual commit made right now, instead of at its last
47
+ * real commit. Both a dirty `sources` path and a dirty DOC read their
48
+ * commit time from that same shared instant, so a pre-commit run reports
49
+ * the verdict CI will report once the commit lands -- for either rule.
50
+ * Stored on `ctx.dirtyAsNow`; see its jsdoc in `src/types.ts` (the
51
+ * canonical description of the model) and the README's "Uncommitted
52
+ * edits (`--dirty-as-now`)" section.
53
+ */
54
+ dirtyAsNow?: boolean;
43
55
  /** Test-only override for git access; production code shells out to the real `git` binary. */
44
56
  runGit?: RunGit;
45
57
  }
package/dist/cli.js CHANGED
@@ -36,6 +36,9 @@ export function runCheck(bundleDir, options = {}) {
36
36
  if (options.futureSkewMinutes !== undefined) {
37
37
  ctx.freshnessFutureSkewSeconds = Math.round(options.futureSkewMinutes * 60);
38
38
  }
39
+ if (options.dirtyAsNow) {
40
+ ctx.dirtyAsNow = true;
41
+ }
39
42
  const findings = allRules.flatMap((rule) => rule.run(ctx));
40
43
  const summary = summarize(findings);
41
44
  const exitCode = summary.errors > 0 || (Boolean(options.strict) && summary.warnings > 0)
@@ -72,6 +75,9 @@ program
72
75
  "the remedy to lift it into a backtick citation or a symbol name (ignored unless --prose-line-references is also passed)")
73
76
  .option("--future-skew-minutes <n>", "sources-fresh-future: clock-skew allowance in minutes before a doc `timestamp` later than " +
74
77
  "the doc's own last commit is flagged as future-dated (default 10)")
78
+ .option("--dirty-as-now", "sources-fresh + sources-fresh-future: model every uncommitted change (modified, staged, or " +
79
+ "untracked) -- a `sources` path and the doc itself alike -- as one virtual commit made right " +
80
+ "now, so a pre-commit run matches what CI reports after the commit lands (opt-in, see README)")
75
81
  .exitOverride()
76
82
  .action((bundleDir, opts) => {
77
83
  try {
@@ -95,6 +101,7 @@ program
95
101
  proseLineReferences: opts.proseLineReferences,
96
102
  proseLineReferencesStrict: opts.proseLineReferencesStrict,
97
103
  futureSkewMinutes,
104
+ dirtyAsNow: opts.dirtyAsNow,
98
105
  });
99
106
  const output = opts.json
100
107
  ? renderJson(result.bundleDir, result.findings)
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -29,9 +29,16 @@ export declare const sourcesFreshRule: Rule;
29
29
  * rather than duplicating it) and no valid `timestamp` (`sources-fresh`
30
30
  * already reports that per-doc notice, so this rule silently skips such a
31
31
  * doc rather than reporting it twice). An uncommitted doc (no own commit
32
- * yet) is likewise "unknown, not flagged": there is no real commit time to
33
- * compare the timestamp against, and flagging every hand-authored,
34
- * not-yet-committed doc as "future-dated" would be a false positive on
35
- * every fresh draft.
32
+ * yet) is likewise "unknown, not flagged" by DEFAULT: there is no real
33
+ * commit time to compare the timestamp against, and flagging every
34
+ * hand-authored, not-yet-committed doc as "future-dated" would be a false
35
+ * positive on every fresh draft. Under `--dirty-as-now` (`ctx.dirtyAsNow`),
36
+ * this changes: `getDocCommitEpochShared` below substitutes the shared
37
+ * virtual-commit "now" instant for ANY dirty doc's epoch (untracked
38
+ * included -- see `commitEpochWithDirtyAsNow`), so a dirty doc DOES get a
39
+ * real comparison here, exactly the working-tree parity `--dirty-as-now`
40
+ * exists for: a `timestamp` re-stamped to "now" reads fresh (within the
41
+ * skew allowance), while one still carrying an implausible future value
42
+ * gets caught before the commit that would otherwise make it so in CI.
36
43
  */
37
44
  export declare const sourcesFreshFutureRule: Rule;
@@ -39,17 +39,39 @@ export const sourcesFreshRule = {
39
39
  }
40
40
  const repoRoot = ctx.repoRoot;
41
41
  const git = ctx.runGit ?? defaultRunGit;
42
+ // `--dirty-as-now` asked for, but git could not tell us what is dirty
43
+ // (a `status` past RunGit's output cap, a `rev-parse` failure, git
44
+ // missing): every path below then falls through to committed history,
45
+ // which is the RIGHT fallback but the WRONG silence -- "the flag found
46
+ // nothing dirty" and "the flag never ran" produce the identical empty
47
+ // finding list. Say so once, bundle-level, in the same style as the
48
+ // "not inside a git work tree" notice above (and, like that one,
49
+ // emitted only here: `sources-fresh-future` shares the same degraded
50
+ // state and relies on this single notice instead of duplicating it).
51
+ if (ctx.dirtyAsNow && getDirtyStateShared(ctx, git, repoRoot) === null) {
52
+ findings.push({
53
+ ruleId: RULE_ID,
54
+ severity: "notice",
55
+ file: "",
56
+ message: "`--dirty-as-now` not applied: git status could not be read, judging by committed history only",
57
+ });
58
+ }
42
59
  // One git call per unique source path across all docs, even though a
43
60
  // STALE/untracked finding is reported per (doc, path) below.
44
61
  const commitEpochCache = new Map();
45
- const commitEpochFor = (source) => {
62
+ // `--dirty-as-now`: every dirty path's "commit epoch" is the SAME
63
+ // virtual-commit instant, shared (by `ctx` identity) with
64
+ // `sources-fresh-future`'s doc-epoch lookup below -- see
65
+ // `commitEpochWithDirtyAsNow`'s doc comment for why this lives in one
66
+ // place instead of being reimplemented per rule.
67
+ const commitEpochFor = (source) => commitEpochWithDirtyAsNow(ctx, git, repoRoot, source, () => {
46
68
  const cached = commitEpochCache.get(source);
47
69
  if (cached !== undefined)
48
70
  return cached;
49
71
  const epoch = getLastCommitEpoch(git, repoRoot, source);
50
72
  commitEpochCache.set(source, epoch);
51
73
  return epoch;
52
- };
74
+ });
53
75
  for (const { doc, sources } of docsWithSources) {
54
76
  const timestampEpoch = getTimestampEpoch(doc.frontmatter.parsed);
55
77
  if (timestampEpoch === undefined) {
@@ -72,23 +94,47 @@ export const sourcesFreshRule = {
72
94
  // rules don't each spawn their own `git log` for the same doc in one
73
95
  // `check` run.
74
96
  //
75
- // GIT PROCESS BUDGET, per doc, per `check` run: 1 (the shared
76
- // `git log -1 --format=%ct` epoch lookup, always) + at most 4 more on
77
- // the re-stamp path (`git log -1 --format=%H%n%P`, then `git diff-tree`,
78
- // then two `git show`s), i.e. AT MOST 5 -- regardless of how many
79
- // sources the doc declares, since both lookups are memoized per doc.
80
- // A doc created by its last commit (or by the repo's root commit)
81
- // stops after 2 (or 1). Plus one `git log` per UNIQUE source path
82
- // across the whole bundle (commitEpochCache above). Pinned by
83
- // "spends at most five git processes per doc" in
84
- // test/sources-fresh.test.ts. PLUS: at most 1 `git rev-parse
85
- // --is-shallow-repository` for the ENTIRE run, not per doc (see
86
- // isShallowRepoShared below) -- only spent at all when some doc's
87
- // re-stamp lookup actually reaches a root commit.
97
+ // GIT PROCESS BUDGET per doc per `check` run, `--dirty-as-now` OFF: 1
98
+ // (the shared `git log -1 --format=%ct` epoch lookup) + at most 4 on
99
+ // the re-stamp path (`git log -1 --format=%H%n%P`, `git diff-tree`,
100
+ // two `git show`s), i.e. AT MOST 5 regardless of how many sources the
101
+ // doc declares, since both lookups are memoized per doc. Plus one `git
102
+ // log` per UNIQUE source path across the bundle (commitEpochCache),
103
+ // and, per RUN rather than per doc, at most one `git rev-parse
104
+ // --show-prefix` (getShowPrefixShared, spent the first time any
105
+ // re-stamp lookup needs the top-level path frame) and at most one
106
+ // `git rev-parse --is-shallow-repository` (isShallowRepoShared).
107
+ // Pinned by "spends at most five git processes per doc" in
108
+ // test/sources-fresh.test.ts.
109
+ //
110
+ // With `--dirty-as-now` ON, add one `git status` per run (the
111
+ // show-prefix read above is shared with it, see readDirtyState) plus,
112
+ // PER DOC, at most one `git ls-tree` and one `git show`, spent only
113
+ // when that doc is itself dirty AND some source of its reads stale.
88
114
  const repoRelDocPath = toRepoRelDocPath(repoRoot, ctx.bundleDir, doc);
89
115
  const docCommitEpochFor = () => getDocCommitEpochShared(ctx, git, repoRoot, repoRelDocPath);
116
+ const showPrefixFor = () => getShowPrefixShared(ctx, git, repoRoot);
117
+ // The single decision point for "did the doc's last commit, real or
118
+ // virtual, actually re-stamp it". A dirty doc (under `--dirty-as-now`)
119
+ // is judged by its WORKING-TREE value against the value committed at
120
+ // HEAD, the virtual commit's implicit first parent
121
+ // (`dirtyDocRestampVerdict`); any other doc is judged against its real
122
+ // last commit's first parent (`restampedByOwnLastCommit`). Because
123
+ // `docCommitEpochFor` already reports a dirty doc's epoch as "now"
124
+ // (`commitEpochWithDirtyAsNow`), the ordinary `docCommitEpoch >=
125
+ // commitEpoch` gate below fires for a dirty doc exactly as for a real
126
+ // co-commit, so ONE verdict function is picked here rather than two
127
+ // mechanisms answering overlapping questions.
90
128
  let restampMemo;
91
- const restampFor = () => (restampMemo ??= restampedByOwnLastCommit(git, repoRoot, repoRelDocPath, () => isShallowRepoShared(ctx, git, repoRoot)));
129
+ const restampFor = () => {
130
+ if (restampMemo !== undefined)
131
+ return restampMemo;
132
+ restampMemo =
133
+ ctx.dirtyAsNow && isDirtyForShared(ctx, git, repoRoot, repoRelDocPath)
134
+ ? dirtyDocRestampVerdict(git, repoRoot, repoRelDocPath, getTimestampIdentity(doc.frontmatter.parsed), showPrefixFor)
135
+ : restampedByOwnLastCommit(git, repoRoot, repoRelDocPath, () => isShallowRepoShared(ctx, git, repoRoot), showPrefixFor);
136
+ return restampMemo;
137
+ };
92
138
  // At most one "not assessable" notice per doc, however many of its
93
139
  // sources hit the unanswerable re-stamp question.
94
140
  let notAssessableReported = false;
@@ -108,14 +154,16 @@ export const sourcesFreshRule = {
108
154
  continue;
109
155
  }
110
156
  let isStale = commitEpoch > timestampEpoch;
111
- // Doc committed at/after the source, AND that commit actually
112
- // re-stamped it: not stale (see comment above). A doc without git
113
- // history (null epoch, e.g. uncommitted) keeps the frontmatter-only
114
- // comparison. When git cannot answer the re-stamp question at all,
115
- // the doc is reported as not assessable rather than being guessed
116
- // either way: calling it STALE would turn a git hiccup into a red
117
- // build, and calling it fresh would be a silent pass.
118
157
  if (isStale) {
158
+ // Doc committed (or, under `--dirty-as-now`, virtually committed
159
+ // right now) at/after the source, AND that commit actually
160
+ // re-stamped it: not stale (see comment above). A doc without git
161
+ // history (null epoch, e.g. uncommitted and clean, no dirty
162
+ // status at all) keeps the frontmatter-only comparison. When git
163
+ // cannot answer the re-stamp question at all, the doc is reported
164
+ // as not assessable rather than being guessed either way: calling
165
+ // it STALE would turn a git hiccup into a red build, and calling
166
+ // it fresh would be a silent pass.
119
167
  const docCommitEpoch = docCommitEpochFor();
120
168
  if (docCommitEpoch !== null && docCommitEpoch >= commitEpoch) {
121
169
  const verdict = restampFor();
@@ -173,10 +221,17 @@ export const sourcesFreshRule = {
173
221
  * rather than duplicating it) and no valid `timestamp` (`sources-fresh`
174
222
  * already reports that per-doc notice, so this rule silently skips such a
175
223
  * doc rather than reporting it twice). An uncommitted doc (no own commit
176
- * yet) is likewise "unknown, not flagged": there is no real commit time to
177
- * compare the timestamp against, and flagging every hand-authored,
178
- * not-yet-committed doc as "future-dated" would be a false positive on
179
- * every fresh draft.
224
+ * yet) is likewise "unknown, not flagged" by DEFAULT: there is no real
225
+ * commit time to compare the timestamp against, and flagging every
226
+ * hand-authored, not-yet-committed doc as "future-dated" would be a false
227
+ * positive on every fresh draft. Under `--dirty-as-now` (`ctx.dirtyAsNow`),
228
+ * this changes: `getDocCommitEpochShared` below substitutes the shared
229
+ * virtual-commit "now" instant for ANY dirty doc's epoch (untracked
230
+ * included -- see `commitEpochWithDirtyAsNow`), so a dirty doc DOES get a
231
+ * real comparison here, exactly the working-tree parity `--dirty-as-now`
232
+ * exists for: a `timestamp` re-stamped to "now" reads fresh (within the
233
+ * skew allowance), while one still carrying an implausible future value
234
+ * gets caught before the commit that would otherwise make it so in CI.
180
235
  */
181
236
  export const sourcesFreshFutureRule = {
182
237
  id: FUTURE_RULE_ID,
@@ -267,33 +322,333 @@ function getLastCommitEpoch(git, repoRoot, source) {
267
322
  const epoch = Number.parseInt(out, 10);
268
323
  return Number.isNaN(epoch) ? null : epoch;
269
324
  }
325
+ /**
326
+ * The full SET of top-level-relative paths `git status` reports as dirty
327
+ * (modified, staged, or untracked) across the WHOLE work tree, read via a
328
+ * SINGLE `status` process per `check` run (never per source or per doc).
329
+ * Used only behind `--dirty-as-now`: a dirty path has no "last commit" yet
330
+ * that reflects its current content, so `sources-fresh` substitutes the
331
+ * current time for it instead of `getLastCommitEpoch`'s necessarily stale
332
+ * answer.
333
+ *
334
+ * `--no-optional-locks`: a read-only check (often a hook or CI step) must
335
+ * not rewrite the index as a side effect. No `--ignored`: an ignored
336
+ * source keeps its "untracked by git, staleness unknown" notice.
337
+ *
338
+ * `--untracked-files=all` is load-bearing. Under git's default untracked
339
+ * mode a brand-new directory collapses into ONE `? newdir/` record and the
340
+ * files inside it are never listed, while the matcher (`isDirtyForShared`)
341
+ * matches a queried path that IS an entry or is an ANCESTOR of one -- so a
342
+ * source or doc INSIDE a new directory matched nothing and silently kept
343
+ * its committed-history verdict, the local/CI divergence this flag exists
344
+ * to remove. The cost is one record per untracked file; a status past
345
+ * RunGit's 16 MiB cap resolves to null like any other git failure and is
346
+ * reported, never treated as "nothing is dirty".
347
+ *
348
+ * `--porcelain=v2 -z` rather than v1: RunGit `.trim()`s every output, and a
349
+ * v1 line for an unstaged-only change starts with a LITERAL SPACE (` M
350
+ * path`), so as the first line its leading field byte would be eaten and
351
+ * the path mis-cut. Every v2 record starts with a non-whitespace kind
352
+ * byte, and NUL delimiting keeps a path with a space, tab or non-ASCII
353
+ * byte intact. Record shapes (fixed fields are read positionally, never
354
+ * re-split by content): `? path`; `1 XY sub mH mI mW hH hI path` (8 fixed
355
+ * fields); `2 XY sub mH mI mW hH hI score path\0origPath` (9 fixed fields,
356
+ * origPath is the FOLLOWING token; both sides of a rename count as dirty);
357
+ * `u XY sub m1 m2 m3 mW h1 h2 h3 path` (10 fixed fields).
358
+ *
359
+ * A failed git call (either one) makes the WHOLE state null: every path
360
+ * then keeps its ordinary commit-epoch verdict, and the rule surfaces that
361
+ * once as the bundle-level "`--dirty-as-now` not applied" notice, since
362
+ * "the flag silently did nothing" and "nothing was dirty" are otherwise
363
+ * indistinguishable to a reader of an empty finding list.
364
+ */
365
+ function readDirtyState(ctx, git, repoRoot) {
366
+ const prefix = getShowPrefixShared(ctx, git, repoRoot);
367
+ if (prefix === null)
368
+ return null;
369
+ const out = git([
370
+ "--no-optional-locks",
371
+ "status",
372
+ "--porcelain=v2",
373
+ "-z",
374
+ "--untracked-files=all",
375
+ ], repoRoot);
376
+ if (out === null)
377
+ return null;
378
+ const paths = new Set();
379
+ // `-z` NUL-terminates every record, including the last -- RunGit's
380
+ // `.trim()` does not strip NUL, so splitting always leaves one empty
381
+ // trailing token.
382
+ const tokens = out.split("\0").filter((t) => t !== "");
383
+ let i = 0;
384
+ while (i < tokens.length) {
385
+ const token = tokens[i];
386
+ const kind = token[0];
387
+ if (kind === "?") {
388
+ paths.add(normalizeRelPath(token.slice(2)));
389
+ i += 1;
390
+ }
391
+ else if (kind === "1") {
392
+ paths.add(normalizeRelPath(token.split(" ").slice(8).join(" ")));
393
+ i += 1;
394
+ }
395
+ else if (kind === "2") {
396
+ paths.add(normalizeRelPath(token.split(" ").slice(9).join(" ")));
397
+ const origPath = tokens[i + 1];
398
+ if (origPath !== undefined)
399
+ paths.add(normalizeRelPath(origPath));
400
+ i += 2;
401
+ }
402
+ else if (kind === "u") {
403
+ paths.add(normalizeRelPath(token.split(" ").slice(10).join(" ")));
404
+ i += 1;
405
+ }
406
+ else {
407
+ // An unrecognized record kind (should not occur without --ignored,
408
+ // which is never passed): skip rather than mis-parse it as a path.
409
+ i += 1;
410
+ }
411
+ }
412
+ return { prefix, paths };
413
+ }
414
+ /**
415
+ * Canonicalizes a repo-relative path spelling before it is used as a dirty
416
+ * lookup key or matched against one: strips a leading `./` and a trailing
417
+ * `/`, converts backslashes, and collapses `.`/`..` segments and duplicate
418
+ * slashes via `path.posix.normalize`. `git status` always reports canonical
419
+ * forward-slash paths, but a frontmatter-authored `sources` entry is under
420
+ * no such obligation: `./srcdir` and `srcdir/` name the same directory
421
+ * `srcdir` does and MUST match the same dirty entries (a raw `srcdir/`
422
+ * would build a self-defeating `srcdir//` prefix, a raw `./srcdir` never
423
+ * matches at all). Applied on BOTH sides of every dirty-path comparison in
424
+ * this file.
425
+ *
426
+ * `.`, `./` and the empty string all normalize to `.`, the root directory
427
+ * itself: a legal `sources` spelling but never a `git status` entry, so
428
+ * `isDirtyForShared` answers it by containment instead of by lookup.
429
+ */
430
+ function normalizeRelPath(relPath) {
431
+ let p = relPath.replace(/\\/g, "/");
432
+ while (p.startsWith("./"))
433
+ p = p.slice(2);
434
+ p = path.posix.normalize(p);
435
+ while (p.length > 1 && p.endsWith("/"))
436
+ p = p.slice(0, -1);
437
+ return p;
438
+ }
439
+ /**
440
+ * Per-run cache (keyed by `BundleContext`, same pattern as
441
+ * `docCommitEpochCache` below) of `readDirtyState`'s answer, so the WHOLE
442
+ * `check` run -- both rules, every doc, every source -- spends at most one
443
+ * `git status` (plus one `git rev-parse --show-prefix`) process, not one
444
+ * per rule. A cached `null` (the git read failed) is a real cached answer,
445
+ * hence the `.has` check rather than a truthiness test: a failed read is
446
+ * not retried per source.
447
+ */
448
+ const dirtyStateCache = new WeakMap();
449
+ function getDirtyStateShared(ctx, git, repoRoot) {
450
+ if (dirtyStateCache.has(ctx))
451
+ return dirtyStateCache.get(ctx) ?? null;
452
+ const state = readDirtyState(ctx, git, repoRoot);
453
+ dirtyStateCache.set(ctx, state);
454
+ return state;
455
+ }
456
+ /**
457
+ * Whether `relPath` (a `sources` entry OR a doc's own path relative to
458
+ * `ctx.repoRoot`, in any spelling `normalizeRelPath` accepts) is dirty per
459
+ * `git status`. The queried path is first respelled in the top-level frame
460
+ * every reported entry uses (`toTopLevelPath`; a no-op when `--repo-root`
461
+ * IS the top level). A dirty FILE then matches by exact normalized path; a
462
+ * dirty DIRECTORY source matches if any reported path lives under it (a
463
+ * `/`-terminated prefix, so `srcdirX/x.ts` never matches `srcdir`). `.`
464
+ * names the root directory itself, which `git status` never reports as an
465
+ * entry, so it is matched by containment: any dirty path at all.
466
+ *
467
+ * A failed state read (`null`) is "nothing is dirty": every path keeps its
468
+ * ordinary commit-epoch verdict, and the rule reports the failure once at
469
+ * bundle level (see `readDirtyState`).
470
+ */
471
+ function isDirtyForShared(ctx, git, repoRoot, relPath) {
472
+ const state = getDirtyStateShared(ctx, git, repoRoot);
473
+ if (state === null)
474
+ return false;
475
+ const { paths } = state;
476
+ const normalized = normalizeRelPath(toTopLevelPath(state.prefix, relPath));
477
+ if (normalized === ".")
478
+ return paths.size > 0;
479
+ if (paths.has(normalized))
480
+ return true;
481
+ const prefix = `${normalized}/`;
482
+ for (const p of paths) {
483
+ if (p.startsWith(prefix))
484
+ return true;
485
+ }
486
+ return false;
487
+ }
488
+ /**
489
+ * Per-run cache (keyed by `BundleContext`) of the SINGLE "now" instant
490
+ * `--dirty-as-now` substitutes for every dirty path's commit epoch, across
491
+ * BOTH rules and every doc/source they assess: one `check` run models every
492
+ * dirty path as landing in the same virtual commit.
493
+ *
494
+ * THE CACHE IS THE GUARANTEE, not the order the callers happen to run in:
495
+ * the rescue gate is an INEQUALITY (`docCommitEpoch >= commitEpoch`) whose
496
+ * two sides are BOTH this value when a dirty doc is paired with a dirty
497
+ * source, and reading the clock afresh per side would let a second boundary
498
+ * between the two reads flip it into a silent, unreproducible STALE
499
+ * depending on which side asked first. The clock is read exactly ONCE per
500
+ * run (`ctx.now`, defaulting to `Date.now`, injectable so a test can pin
501
+ * that count); every caller wanting "now" goes through this accessor.
502
+ */
503
+ const nowEpochCache = new WeakMap();
504
+ function getNowEpochShared(ctx) {
505
+ let epoch = nowEpochCache.get(ctx);
506
+ if (epoch === undefined) {
507
+ epoch = Math.floor((ctx.now ?? Date.now)() / 1000);
508
+ nowEpochCache.set(ctx, epoch);
509
+ }
510
+ return epoch;
511
+ }
512
+ /**
513
+ * Per-run cache of `git rev-parse --show-prefix` for `repoRoot`: `""` when
514
+ * `repoRoot` IS the repository's top level, otherwise its path relative to
515
+ * the top level with a trailing slash (`sub/`, `a/b/`); `null` when git
516
+ * could not answer. Read at most once per run, and by everything in this
517
+ * file that has to speak in the TOP-LEVEL path frame.
518
+ *
519
+ * THE INVARIANT: every path this file receives is relative to `repoRoot`,
520
+ * but `git status` entries, `git diff-tree --name-status` entries and the
521
+ * `<rev>:<path>` blob spelling `git show` takes are all top-level-relative
522
+ * (a bare `<rev>:<path>` is NOT resolved against the cwd). The two frames
523
+ * coincide only when `--repo-root` names the top level. Under a
524
+ * `--repo-root` naming a SUBDIRECTORY, a root-relative path used in the
525
+ * top-level frame either matches nothing (a blob read fails, a dirty path
526
+ * never matches) or, worse, reads a SAME-NAMED file at the top level in
527
+ * the doc's place and judges the doc by a stranger's stamp. So every
528
+ * cross-frame step goes through `toTopLevelPath` with this prefix, never
529
+ * a bare path.
530
+ */
531
+ const showPrefixCache = new WeakMap();
532
+ function getShowPrefixShared(ctx, git, repoRoot) {
533
+ if (showPrefixCache.has(ctx))
534
+ return showPrefixCache.get(ctx) ?? null;
535
+ // `""` (this IS the top level) is a valid answer; only an outright
536
+ // failure is null.
537
+ const raw = git(["rev-parse", "--show-prefix"], repoRoot);
538
+ const prefix = raw === null ? null : raw === "" ? "" : `${normalizeRelPath(raw)}/`;
539
+ showPrefixCache.set(ctx, prefix);
540
+ return prefix;
541
+ }
542
+ /** `rootRelPath` (relative to `repoRoot`) respelled in the top-level frame `showPrefix` (from `getShowPrefixShared`) describes. */
543
+ function toTopLevelPath(showPrefix, rootRelPath) {
544
+ return `${showPrefix}${rootRelPath}`;
545
+ }
546
+ /**
547
+ * THE one place a blob is read: `git show <rev>:<topLevelPath>`. The path
548
+ * MUST already be in the top-level frame (`toTopLevelPath`), see
549
+ * `getShowPrefixShared`. Null on any git failure, "no such path at that
550
+ * rev" included.
551
+ */
552
+ function showBlob(git, repoRoot, rev, topLevelPath) {
553
+ return git(["show", `${rev}:${topLevelPath}`], repoRoot);
554
+ }
555
+ /**
556
+ * THE single choke point where `--dirty-as-now`'s virtual-commit model is
557
+ * applied: a dirty `relPath` (source or doc, see `isDirtyForShared`) is
558
+ * judged as committed at the shared "now" instant (`getNowEpochShared`)
559
+ * instead of consulting `actualEpoch` at all; anything else -- the flag
560
+ * off, or `relPath` not dirty -- defers to `actualEpoch` (typically a
561
+ * memoized real `git log` lookup), byte-identical to pre-flag behavior.
562
+ * Every commit-epoch read in this file that is meant to honor
563
+ * `--dirty-as-now` MUST go through this function (directly, or via
564
+ * `getDocCommitEpochShared` below, which is itself built on it) rather
565
+ * than re-implementing the dirty check inline, so a source's epoch and a
566
+ * doc's own epoch are always read the same way, by both rules.
567
+ */
568
+ function commitEpochWithDirtyAsNow(ctx, git, repoRoot, relPath, actualEpoch) {
569
+ if (ctx.dirtyAsNow && isDirtyForShared(ctx, git, repoRoot, relPath)) {
570
+ return getNowEpochShared(ctx);
571
+ }
572
+ return actualEpoch();
573
+ }
574
+ /**
575
+ * `--dirty-as-now` only: the verdict for "did the doc's OWN uncommitted
576
+ * working-tree state re-stamp it" -- the virtual-commit analogue of
577
+ * `restampedByOwnLastCommit`'s co-commit check, used in its place (see
578
+ * `restampFor` in the rule above) whenever the doc itself is dirty. The
579
+ * virtual commit's implicit "first parent" is simply HEAD, so this compares
580
+ * the doc's CURRENT (working-tree) parsed `timestamp` identity -- already
581
+ * read from disk by the caller, since `BundleDoc` always reflects the
582
+ * working tree, dirty or not -- against the value committed at HEAD for the
583
+ * same path. Any change of value, in EITHER direction (including a
584
+ * backwards re-stamp), counts as re-stamped, mirroring the committed
585
+ * rescue's "changed, not corrected" semantics documented on
586
+ * `restampedByOwnLastCommit` below.
587
+ *
588
+ * Distinguishes a genuinely untracked/new doc (no entry for this path at
589
+ * HEAD at all -- `git ls-tree HEAD -- <path>` succeeds with EMPTY output,
590
+ * never a git failure) from a real git failure reading an EXISTING HEAD
591
+ * blob (`git show` itself failing -- a corrupt object, an unreadable blob):
592
+ * the former counts as "restamped" (there is no prior committed value to
593
+ * compare against, so its whole content, stamp included, is new -- the
594
+ * working-tree equivalent of "the doc being created there"); the latter is
595
+ * `unknown`, falling through to the rule's existing not-assessable notice
596
+ * rather than being silently treated the same as "created". `git ls-tree`
597
+ * itself failing (an unborn HEAD, git unavailable) is likewise `unknown`.
598
+ */
599
+ function dirtyDocRestampVerdict(git, repoRoot, repoRelDocPath, onDiskTimestamp, showPrefix) {
600
+ // A pathspec (`--`), cwd-relative: stays in the root-relative frame.
601
+ const treeEntry = git(["ls-tree", "-z", "HEAD", "--", repoRelDocPath], repoRoot);
602
+ if (treeEntry === null)
603
+ return "unknown";
604
+ if (treeEntry === "")
605
+ return "restamped";
606
+ const prefix = showPrefix();
607
+ if (prefix === null)
608
+ return "unknown";
609
+ const committed = showBlob(git, repoRoot, "HEAD", toTopLevelPath(prefix, repoRelDocPath));
610
+ if (committed === null)
611
+ return "unknown";
612
+ const committedStamp = getTimestampIdentity(parseFrontmatter(committed).frontmatter.parsed);
613
+ return committedStamp !== onDiskTimestamp ? "restamped" : "not-restamped";
614
+ }
270
615
  function epochToIso(epochSeconds) {
271
616
  return new Date(epochSeconds * 1000).toISOString();
272
617
  }
273
618
  /**
274
- * Per-run cache of a doc's own last-commit epoch, keyed by the
275
- * `BundleContext` instance so `sources-fresh`'s doc-commit comparison and
276
- * `sources-fresh-future`'s timestamp comparison -- both need the IDENTICAL
277
- * (repoRoot, doc path) `getLastCommitEpoch` lookup for every doc in one
278
- * `check` invocation -- share one `git log` process per doc instead of each
279
- * rule spawning its own. Safe to key on the context object itself: a fresh
280
- * `BundleContext` is built per `runCheck`/`loadBundle` call, so nothing
281
- * reuses a stale cache entry across invocations, and the WeakMap lets the
282
- * cache be garbage-collected with the context once a run is done.
619
+ * Per-run cache of a doc's own REAL (committed) last-commit epoch, keyed by
620
+ * the `BundleContext` instance so `sources-fresh`'s doc-commit comparison
621
+ * and `sources-fresh-future`'s timestamp comparison -- both need the
622
+ * IDENTICAL (repoRoot, doc path) `getLastCommitEpoch` lookup for every doc
623
+ * in one `check` invocation -- share one `git log` process per doc instead
624
+ * of each rule spawning its own. Safe to key on the context object itself:
625
+ * a fresh `BundleContext` is built per `runCheck`/`loadBundle` call, so
626
+ * nothing reuses a stale cache entry across invocations, and the WeakMap
627
+ * lets the cache be garbage-collected with the context once a run is done.
628
+ *
629
+ * `getDocCommitEpochShared` below is the single place BOTH rules read a
630
+ * doc's commit epoch from, and it is itself built on
631
+ * `commitEpochWithDirtyAsNow`: under `--dirty-as-now`, a dirty doc's epoch
632
+ * is the shared virtual "now" instant instead of this real cache's answer,
633
+ * so `sources-fresh`'s co-commit rescue and `sources-fresh-future`'s
634
+ * timestamp comparison always see the identical (real-or-virtual) epoch
635
+ * for the same doc in the same `check` run.
283
636
  */
284
637
  const docCommitEpochCache = new WeakMap();
285
638
  function getDocCommitEpochShared(ctx, git, repoRoot, repoRelDocPath) {
286
- let cache = docCommitEpochCache.get(ctx);
287
- if (!cache) {
288
- cache = new Map();
289
- docCommitEpochCache.set(ctx, cache);
290
- }
291
- const cached = cache.get(repoRelDocPath);
292
- if (cached !== undefined)
293
- return cached;
294
- const epoch = getLastCommitEpoch(git, repoRoot, repoRelDocPath);
295
- cache.set(repoRelDocPath, epoch);
296
- return epoch;
639
+ return commitEpochWithDirtyAsNow(ctx, git, repoRoot, repoRelDocPath, () => {
640
+ let cache = docCommitEpochCache.get(ctx);
641
+ if (!cache) {
642
+ cache = new Map();
643
+ docCommitEpochCache.set(ctx, cache);
644
+ }
645
+ const cached = cache.get(repoRelDocPath);
646
+ if (cached !== undefined)
647
+ return cached;
648
+ const epoch = getLastCommitEpoch(git, repoRoot, repoRelDocPath);
649
+ cache.set(repoRelDocPath, epoch);
650
+ return epoch;
651
+ });
297
652
  }
298
653
  /**
299
654
  * Per-run cache (keyed by `BundleContext`, same pattern as
@@ -370,7 +725,7 @@ function isShallowRepoShared(ctx, git, repoRoot) {
370
725
  * (skipped for a root commit), and 2 blob reads (skipped when the doc was
371
726
  * created there). See the GIT PROCESS BUDGET comment in the rule above.
372
727
  */
373
- function restampedByOwnLastCommit(git, repoRoot, repoRelDocPath, isShallowRepo) {
728
+ function restampedByOwnLastCommit(git, repoRoot, repoRelDocPath, isShallowRepo, showPrefix) {
374
729
  // %H then %P on its own line: the doc's last commit and its parent list in
375
730
  // ONE process. Default history simplification is exactly what this needs
376
731
  // for a single path: a merge whose result for that path differs from every
@@ -397,15 +752,22 @@ function restampedByOwnLastCommit(git, repoRoot, repoRelDocPath, isShallowRepo)
397
752
  return isShallowRepo() ? "unknown-shallow-root" : "restamped";
398
753
  }
399
754
  const firstParent = parents[0];
400
- const previous = previousPathIn(git, repoRoot, firstParent, sha, repoRelDocPath);
755
+ // From here on everything git reports or takes is in the top-level frame
756
+ // (see getShowPrefixShared): the diff-tree entries previousPathIn matches
757
+ // against, and both `<rev>:<path>` blob reads.
758
+ const prefix = showPrefix();
759
+ if (prefix === null)
760
+ return "unknown";
761
+ const topLevelDocPath = toTopLevelPath(prefix, repoRelDocPath);
762
+ const previous = previousPathIn(git, repoRoot, firstParent, sha, topLevelDocPath);
401
763
  if (previous.kind === "unknown")
402
764
  return "unknown";
403
765
  if (previous.kind === "created")
404
766
  return "restamped";
405
- const current = git(["show", `${sha}:${repoRelDocPath}`], repoRoot);
767
+ const current = showBlob(git, repoRoot, sha, topLevelDocPath);
406
768
  if (current === null)
407
769
  return "unknown";
408
- const before = git(["show", `${firstParent}:${previous.path}`], repoRoot);
770
+ const before = showBlob(git, repoRoot, firstParent, previous.path);
409
771
  if (before === null)
410
772
  return "unknown";
411
773
  const currentStamp = getTimestampIdentity(parseFrontmatter(current).frontmatter.parsed);
@@ -418,6 +780,8 @@ function restampedByOwnLastCommit(git, repoRoot, repoRelDocPath, isShallowRepo)
418
780
  * Where the doc lived in `parentSha`, so its previous revision can be read
419
781
  * by that path: the same path (`same`), a different one it was renamed from
420
782
  * (`renamed`), or nowhere at all because that commit created it (`created`).
783
+ * `topLevelDocPath` and every returned path are in the top-level frame,
784
+ * the one `diff-tree --name-status` reports in (see getShowPrefixShared).
421
785
  *
422
786
  * Runs `git diff-tree` WITHOUT a pathspec on purpose. A pathspec is applied
423
787
  * BEFORE rename detection, so `git diff-tree -M --name-status <parent> <sha>
@@ -438,7 +802,7 @@ function restampedByOwnLastCommit(git, repoRoot, repoRelDocPath, isShallowRepo)
438
802
  * `--name-status` output C-QUOTES any path containing a non-ASCII byte
439
803
  * (`core.quotePath` defaults to true) as a double-quoted string with octal
440
804
  * escapes (e.g. `"bundle/\303\266lt.md"` for `bundle/ölt.md`), so a plain
441
- * `repoRelDocPath` never string-equals that quoted form and a non-ASCII doc
805
+ * `topLevelDocPath` never string-equals that quoted form and a non-ASCII doc
442
806
  * falls through every branch below to the `same` fallback, which returns
443
807
  * the doc's real (unquoted) CURRENT path unchanged. When that commit was
444
808
  * actually a rename or creation, the doc was NOT at that path in the first
@@ -451,7 +815,7 @@ function restampedByOwnLastCommit(git, repoRoot, repoRelDocPath, isShallowRepo)
451
815
  * override this rule would otherwise have to remember on every git
452
816
  * invocation touching a path, not just this one).
453
817
  */
454
- function previousPathIn(git, repoRoot, parentSha, sha, repoRelDocPath) {
818
+ function previousPathIn(git, repoRoot, parentSha, sha, topLevelDocPath) {
455
819
  const nameStatus = git([
456
820
  "diff-tree",
457
821
  "-r",
@@ -481,19 +845,19 @@ function previousPathIn(git, repoRoot, parentSha, sha, repoRelDocPath) {
481
845
  const oldPath = tokens[i + 1];
482
846
  const newPath = tokens[i + 2];
483
847
  i += 3;
484
- if (newPath === repoRelDocPath) {
848
+ if (newPath === topLevelDocPath) {
485
849
  return { kind: "renamed", path: oldPath };
486
850
  }
487
851
  continue;
488
852
  }
489
853
  const p = tokens[i + 1];
490
854
  i += 2;
491
- if (p !== repoRelDocPath)
855
+ if (p !== topLevelDocPath)
492
856
  continue;
493
857
  if (status.startsWith("A"))
494
858
  return { kind: "created" };
495
- return { kind: "same", path: repoRelDocPath };
859
+ return { kind: "same", path: topLevelDocPath };
496
860
  }
497
- return { kind: "same", path: repoRelDocPath };
861
+ return { kind: "same", path: topLevelDocPath };
498
862
  }
499
863
  //# sourceMappingURL=sources-fresh.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"sources-fresh.js","sourceRoot":"","sources":["../../src/rules/sources-fresh.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"sources-fresh.js","sourceRoot":"","sources":["../../src/rules/sources-fresh.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EAAE,MAAM,IAAI,aAAa,EAAE,MAAM,WAAW,CAAC;AACpD,OAAO,EACL,qBAAqB,EACrB,iBAAiB,EACjB,oBAAoB,EACpB,eAAe,EACf,gBAAgB,GACjB,MAAM,YAAY,CAAC;AASpB,MAAM,OAAO,GAAG,eAAe,CAAC;AAChC,MAAM,cAAc,GAAG,sBAAsB,CAAC;AAE9C;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,2BAA2B,GAAG,GAAG,CAAC;AAE/C,MAAM,CAAC,MAAM,gBAAgB,GAAS;IACpC,EAAE,EAAE,OAAO;IACX,WAAW,EACT,6vBAA6vB;IAC/vB,GAAG,CAAC,GAAG;QACL,MAAM,QAAQ,GAAc,EAAE,CAAC;QAE/B,MAAM,eAAe,GAAG,kBAAkB,CAAC,GAAG,CAAC,CAAC;QAChD,IAAI,eAAe,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,QAAQ,CAAC;QAElD,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC;YAClB,sEAAsE;YACtE,mEAAmE;YACnE,qEAAqE;YACrE,oEAAoE;YACpE,mEAAmE;YACnE,oEAAoE;YACpE,oBAAoB;YACpB,QAAQ,CAAC,IAAI,CAAC;gBACZ,MAAM,EAAE,OAAO;gBACf,QAAQ,EAAE,QAAQ;gBAClB,IAAI,EAAE,EAAE;gBACR,OAAO,EAAE,+CAA+C;aACzD,CAAC,CAAC;YACH,OAAO,QAAQ,CAAC;QAClB,CAAC;QACD,MAAM,QAAQ,GAAG,GAAG,CAAC,QAAQ,CAAC;QAC9B,MAAM,GAAG,GAAG,GAAG,CAAC,MAAM,IAAI,aAAa,CAAC;QAExC,sEAAsE;QACtE,mEAAmE;QACnE,sEAAsE;QACtE,uEAAuE;QACvE,sEAAsE;QACtE,oEAAoE;QACpE,iEAAiE;QACjE,qEAAqE;QACrE,qEAAqE;QACrE,IAAI,GAAG,CAAC,UAAU,IAAI,mBAAmB,CAAC,GAAG,EAAE,GAAG,EAAE,QAAQ,CAAC,KAAK,IAAI,EAAE,CAAC;YACvE,QAAQ,CAAC,IAAI,CAAC;gBACZ,MAAM,EAAE,OAAO;gBACf,QAAQ,EAAE,QAAQ;gBAClB,IAAI,EAAE,EAAE;gBACR,OAAO,EACL,+FAA+F;aAClG,CAAC,CAAC;QACL,CAAC;QAED,qEAAqE;QACrE,6DAA6D;QAC7D,MAAM,gBAAgB,GAAG,IAAI,GAAG,EAAyB,CAAC;QAC1D,kEAAkE;QAClE,0DAA0D;QAC1D,yDAAyD;QACzD,sEAAsE;QACtE,iDAAiD;QACjD,MAAM,cAAc,GAAG,CAAC,MAAc,EAAiB,EAAE,CACvD,yBAAyB,CAAC,GAAG,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE;YACzD,MAAM,MAAM,GAAG,gBAAgB,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;YAC5C,IAAI,MAAM,KAAK,SAAS;gBAAE,OAAO,MAAM,CAAC;YACxC,MAAM,KAAK,GAAG,kBAAkB,CAAC,GAAG,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;YACxD,gBAAgB,CAAC,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;YACpC,OAAO,KAAK,CAAC;QACf,CAAC,CAAC,CAAC;QAEL,KAAK,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,IAAI,eAAe,EAAE,CAAC;YAC/C,MAAM,cAAc,GAAG,iBAAiB,CAAC,GAAG,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;YACjE,IAAI,cAAc,KAAK,SAAS,EAAE,CAAC;gBACjC,QAAQ,CAAC,IAAI,CAAC;oBACZ,MAAM,EAAE,OAAO;oBACf,QAAQ,EAAE,QAAQ;oBAClB,IAAI,EAAE,GAAG,CAAC,OAAO;oBACjB,OAAO,EAAE,8CAA8C;iBACxD,CAAC,CAAC;gBACH,SAAS;YACX,CAAC;YAED,sEAAsE;YACtE,qEAAqE;YACrE,qEAAqE;YACrE,qEAAqE;YACrE,sEAAsE;YACtE,uEAAuE;YACvE,mEAAmE;YACnE,mEAAmE;YACnE,qEAAqE;YACrE,eAAe;YACf,EAAE;YACF,sEAAsE;YACtE,qEAAqE;YACrE,oEAAoE;YACpE,sEAAsE;YACtE,uEAAuE;YACvE,oEAAoE;YACpE,+DAA+D;YAC/D,gEAAgE;YAChE,kEAAkE;YAClE,iEAAiE;YACjE,2DAA2D;YAC3D,8BAA8B;YAC9B,EAAE;YACF,8DAA8D;YAC9D,sEAAsE;YACtE,oEAAoE;YACpE,oEAAoE;YACpE,MAAM,cAAc,GAAG,gBAAgB,CAAC,QAAQ,EAAE,GAAG,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;YACtE,MAAM,iBAAiB,GAAG,GAAkB,EAAE,CAC5C,uBAAuB,CAAC,GAAG,EAAE,GAAG,EAAE,QAAQ,EAAE,cAAc,CAAC,CAAC;YAC9D,MAAM,aAAa,GAAG,GAAkB,EAAE,CACxC,mBAAmB,CAAC,GAAG,EAAE,GAAG,EAAE,QAAQ,CAAC,CAAC;YAC1C,oEAAoE;YACpE,uEAAuE;YACvE,qEAAqE;YACrE,mDAAmD;YACnD,uEAAuE;YACvE,mEAAmE;YACnE,mEAAmE;YACnE,iEAAiE;YACjE,sEAAsE;YACtE,oEAAoE;YACpE,8CAA8C;YAC9C,IAAI,WAAuC,CAAC;YAC5C,MAAM,UAAU,GAAG,GAAmB,EAAE;gBACtC,IAAI,WAAW,KAAK,SAAS;oBAAE,OAAO,WAAW,CAAC;gBAClD,WAAW;oBACT,GAAG,CAAC,UAAU,IAAI,gBAAgB,CAAC,GAAG,EAAE,GAAG,EAAE,QAAQ,EAAE,cAAc,CAAC;wBACpE,CAAC,CAAC,sBAAsB,CACpB,GAAG,EACH,QAAQ,EACR,cAAc,EACd,oBAAoB,CAAC,GAAG,CAAC,WAAW,CAAC,MAAM,CAAC,EAC5C,aAAa,CACd;wBACH,CAAC,CAAC,wBAAwB,CACtB,GAAG,EACH,QAAQ,EACR,cAAc,EACd,GAAG,EAAE,CAAC,mBAAmB,CAAC,GAAG,EAAE,GAAG,EAAE,QAAQ,CAAC,EAC7C,aAAa,CACd,CAAC;gBACR,OAAO,WAAW,CAAC;YACrB,CAAC,CAAC;YACF,mEAAmE;YACnE,kDAAkD;YAClD,IAAI,qBAAqB,GAAG,KAAK,CAAC;YAElC,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;gBAC7B,mEAAmE;gBACnE,oCAAoC;gBACpC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;oBAAE,SAAS;gBAE1D,MAAM,WAAW,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC;gBAC3C,IAAI,WAAW,KAAK,IAAI,EAAE,CAAC;oBACzB,QAAQ,CAAC,IAAI,CAAC;wBACZ,MAAM,EAAE,OAAO;wBACf,QAAQ,EAAE,QAAQ;wBAClB,IAAI,EAAE,GAAG,CAAC,OAAO;wBACjB,OAAO,EAAE,0CAA0C,MAAM,IAAI;qBAC9D,CAAC,CAAC;oBACH,SAAS;gBACX,CAAC;gBAED,IAAI,OAAO,GAAG,WAAW,GAAG,cAAc,CAAC;gBAE3C,IAAI,OAAO,EAAE,CAAC;oBACZ,iEAAiE;oBACjE,2DAA2D;oBAC3D,kEAAkE;oBAClE,4DAA4D;oBAC5D,iEAAiE;oBACjE,kEAAkE;oBAClE,kEAAkE;oBAClE,iEAAiE;oBACjE,mCAAmC;oBACnC,MAAM,cAAc,GAAG,iBAAiB,EAAE,CAAC;oBAC3C,IAAI,cAAc,KAAK,IAAI,IAAI,cAAc,IAAI,WAAW,EAAE,CAAC;wBAC7D,MAAM,OAAO,GAAG,UAAU,EAAE,CAAC;wBAC7B,IAAI,OAAO,KAAK,WAAW,EAAE,CAAC;4BAC5B,OAAO,GAAG,KAAK,CAAC;wBAClB,CAAC;6BAAM,IACL,OAAO,KAAK,SAAS;4BACrB,OAAO,KAAK,sBAAsB,EAClC,CAAC;4BACD,IAAI,CAAC,qBAAqB,EAAE,CAAC;gCAC3B,qBAAqB,GAAG,IAAI,CAAC;gCAC7B,QAAQ,CAAC,IAAI,CAAC;oCACZ,MAAM,EAAE,OAAO;oCACf,QAAQ,EAAE,QAAQ;oCAClB,IAAI,EAAE,GAAG,CAAC,OAAO;oCACjB,OAAO,EACL,OAAO,KAAK,sBAAsB;wCAChC,CAAC,CAAC,gQAAgQ;wCAClQ,CAAC,CAAC,gHAAgH;iCACvH,CAAC,CAAC;4BACL,CAAC;4BACD,SAAS;wBACX,CAAC;oBACH,CAAC;gBACH,CAAC;gBAED,IAAI,OAAO,EAAE,CAAC;oBACZ,QAAQ,CAAC,IAAI,CAAC;wBACZ,MAAM,EAAE,OAAO;wBACf,QAAQ,EAAE,SAAS;wBACnB,IAAI,EAAE,GAAG,CAAC,OAAO;wBACjB,OAAO,EAAE,YAAY,MAAM,cAAc,UAAU,CAAC,WAAW,CAAC,wBAAwB,UAAU,CAAC,cAAc,CAAC,EAAE;qBACrH,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;QACH,CAAC;QAED,OAAO,QAAQ,CAAC;IAClB,CAAC;CACF,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAS;IAC1C,EAAE,EAAE,cAAc;IAClB,WAAW,EACT,ylBAAylB;IAC3lB,GAAG,CAAC,GAAG;QACL,MAAM,QAAQ,GAAc,EAAE,CAAC;QAE/B,MAAM,eAAe,GAAG,kBAAkB,CAAC,GAAG,CAAC,CAAC;QAChD,IAAI,eAAe,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,QAAQ,CAAC;QAClD,IAAI,CAAC,GAAG,CAAC,QAAQ;YAAE,OAAO,QAAQ,CAAC;QAEnC,MAAM,QAAQ,GAAG,GAAG,CAAC,QAAQ,CAAC;QAC9B,MAAM,GAAG,GAAG,GAAG,CAAC,MAAM,IAAI,aAAa,CAAC;QACxC,MAAM,WAAW,GACf,GAAG,CAAC,0BAA0B,IAAI,2BAA2B,CAAC;QAEhE,KAAK,MAAM,EAAE,GAAG,EAAE,IAAI,eAAe,EAAE,CAAC;YACtC,MAAM,cAAc,GAAG,iBAAiB,CAAC,GAAG,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;YACjE,IAAI,cAAc,KAAK,SAAS;gBAAE,SAAS;YAE3C,qEAAqE;YACrE,iEAAiE;YACjE,4DAA4D;YAC5D,oEAAoE;YACpE,8DAA8D;YAC9D,6DAA6D;YAC7D,kEAAkE;YAClE,qEAAqE;YACrE,gBAAgB;YAChB,MAAM,YAAY,GAAG,qBAAqB,CAAC,GAAG,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;YACnE,IAAI,YAAY,KAAK,SAAS,IAAI,CAAC,gBAAgB,CAAC,YAAY,CAAC,EAAE,CAAC;gBAClE,QAAQ,CAAC,IAAI,CAAC;oBACZ,MAAM,EAAE,cAAc;oBACtB,QAAQ,EAAE,QAAQ;oBAClB,IAAI,EAAE,GAAG,CAAC,OAAO;oBACjB,OAAO,EACL,kIAAkI;iBACrI,CAAC,CAAC;gBACH,SAAS;YACX,CAAC;YAED,MAAM,cAAc,GAAG,gBAAgB,CAAC,QAAQ,EAAE,GAAG,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;YACtE,MAAM,cAAc,GAAG,uBAAuB,CAC5C,GAAG,EACH,GAAG,EACH,QAAQ,EACR,cAAc,CACf,CAAC;YACF,IAAI,cAAc,KAAK,IAAI;gBAAE,SAAS;YAEtC,IAAI,cAAc,GAAG,cAAc,GAAG,WAAW,EAAE,CAAC;gBAClD,QAAQ,CAAC,IAAI,CAAC;oBACZ,MAAM,EAAE,cAAc;oBACtB,QAAQ,EAAE,SAAS;oBACnB,IAAI,EAAE,GAAG,CAAC,OAAO;oBACjB,OAAO,EAAE,+BAA+B,UAAU,CAAC,cAAc,CAAC,uCAAuC,UAAU,CAAC,cAAc,CAAC,oBAAoB,WAAW,IAAI;iBACvK,CAAC,CAAC;YACL,CAAC;QACH,CAAC;QAED,OAAO,QAAQ,CAAC;IAClB,CAAC;CACF,CAAC;AAEF;;;;;GAKG;AACH,SAAS,kBAAkB,CACzB,GAAkB;IAElB,OAAO,GAAG,CAAC,IAAI;SACZ,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,eAAe,CAAC,GAAG,CAAC,WAAW,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;SACzE,MAAM,CACL,CAAC,KAAK,EAAkD,EAAE,CACxD,KAAK,CAAC,OAAO,KAAK,SAAS,CAC9B,CAAC;AACN,CAAC;AAED,yGAAyG;AACzG,SAAS,gBAAgB,CACvB,QAAgB,EAChB,SAAiB,EACjB,GAAc;IAEd,OAAO,IAAI;SACR,QAAQ,CAAC,QAAQ,EAAE,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC;SACrD,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC;SACf,IAAI,CAAC,GAAG,CAAC,CAAC;AACf,CAAC;AAED;;;;;;;;;;;GAWG;AACH,SAAS,kBAAkB,CACzB,GAAW,EACX,QAAgB,EAChB,MAAc;IAEd,MAAM,GAAG,GAAG,GAAG,CAAC,CAAC,KAAK,EAAE,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,CAAC,EAAE,QAAQ,CAAC,CAAC;IACvE,IAAI,CAAC,GAAG;QAAE,OAAO,IAAI,CAAC;IACtB,MAAM,KAAK,GAAG,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;IACvC,OAAO,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC;AAC5C,CAAC;AAiBD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,SAAS,cAAc,CACrB,GAAkB,EAClB,GAAW,EACX,QAAgB;IAEhB,MAAM,MAAM,GAAG,mBAAmB,CAAC,GAAG,EAAE,GAAG,EAAE,QAAQ,CAAC,CAAC;IACvD,IAAI,MAAM,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IACjC,MAAM,GAAG,GAAG,GAAG,CACb;QACE,qBAAqB;QACrB,QAAQ;QACR,gBAAgB;QAChB,IAAI;QACJ,uBAAuB;KACxB,EACD,QAAQ,CACT,CAAC;IACF,IAAI,GAAG,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAC9B,MAAM,KAAK,GAAG,IAAI,GAAG,EAAU,CAAC;IAChC,mEAAmE;IACnE,qEAAqE;IACrE,kBAAkB;IAClB,MAAM,MAAM,GAAG,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC;IACvD,IAAI,CAAC,GAAG,CAAC,CAAC;IACV,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC;QACzB,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;QACxB,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QACtB,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;YACjB,KAAK,CAAC,GAAG,CAAC,gBAAgB,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YAC5C,CAAC,IAAI,CAAC,CAAC;QACT,CAAC;aAAM,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;YACxB,KAAK,CAAC,GAAG,CAAC,gBAAgB,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;YACjE,CAAC,IAAI,CAAC,CAAC;QACT,CAAC;aAAM,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;YACxB,KAAK,CAAC,GAAG,CAAC,gBAAgB,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;YACjE,MAAM,QAAQ,GAAG,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;YAC/B,IAAI,QAAQ,KAAK,SAAS;gBAAE,KAAK,CAAC,GAAG,CAAC,gBAAgB,CAAC,QAAQ,CAAC,CAAC,CAAC;YAClE,CAAC,IAAI,CAAC,CAAC;QACT,CAAC;aAAM,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;YACxB,KAAK,CAAC,GAAG,CAAC,gBAAgB,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;YAClE,CAAC,IAAI,CAAC,CAAC;QACT,CAAC;aAAM,CAAC;YACN,mEAAmE;YACnE,mEAAmE;YACnE,CAAC,IAAI,CAAC,CAAC;QACT,CAAC;IACH,CAAC;IACD,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;AAC3B,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,SAAS,gBAAgB,CAAC,OAAe;IACvC,IAAI,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;IACpC,OAAO,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAC1C,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;IAC5B,OAAO,CAAC,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAC3D,OAAO,CAAC,CAAC;AACX,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,eAAe,GAAG,IAAI,OAAO,EAAoC,CAAC;AAExE,SAAS,mBAAmB,CAC1B,GAAkB,EAClB,GAAW,EACX,QAAgB;IAEhB,IAAI,eAAe,CAAC,GAAG,CAAC,GAAG,CAAC;QAAE,OAAO,eAAe,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC;IACtE,MAAM,KAAK,GAAG,cAAc,CAAC,GAAG,EAAE,GAAG,EAAE,QAAQ,CAAC,CAAC;IACjD,eAAe,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IAChC,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,SAAS,gBAAgB,CACvB,GAAkB,EAClB,GAAW,EACX,QAAgB,EAChB,OAAe;IAEf,MAAM,KAAK,GAAG,mBAAmB,CAAC,GAAG,EAAE,GAAG,EAAE,QAAQ,CAAC,CAAC;IACtD,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IACjC,MAAM,EAAE,KAAK,EAAE,GAAG,KAAK,CAAC;IACxB,MAAM,UAAU,GAAG,gBAAgB,CAAC,cAAc,CAAC,KAAK,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAC3E,IAAI,UAAU,KAAK,GAAG;QAAE,OAAO,KAAK,CAAC,IAAI,GAAG,CAAC,CAAC;IAC9C,IAAI,KAAK,CAAC,GAAG,CAAC,UAAU,CAAC;QAAE,OAAO,IAAI,CAAC;IACvC,MAAM,MAAM,GAAG,GAAG,UAAU,GAAG,CAAC;IAChC,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;QACtB,IAAI,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC;YAAE,OAAO,IAAI,CAAC;IACxC,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,aAAa,GAAG,IAAI,OAAO,EAAyB,CAAC;AAE3D,SAAS,iBAAiB,CAAC,GAAkB;IAC3C,IAAI,KAAK,GAAG,aAAa,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACnC,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACxB,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,EAAE,GAAG,IAAI,CAAC,CAAC;QACnD,aAAa,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IAChC,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,eAAe,GAAG,IAAI,OAAO,EAAgC,CAAC;AAEpE,SAAS,mBAAmB,CAC1B,GAAkB,EAClB,GAAW,EACX,QAAgB;IAEhB,IAAI,eAAe,CAAC,GAAG,CAAC,GAAG,CAAC;QAAE,OAAO,eAAe,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC;IACtE,mEAAmE;IACnE,mBAAmB;IACnB,MAAM,GAAG,GAAG,GAAG,CAAC,CAAC,WAAW,EAAE,eAAe,CAAC,EAAE,QAAQ,CAAC,CAAC;IAC1D,MAAM,MAAM,GACV,GAAG,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,gBAAgB,CAAC,GAAG,CAAC,GAAG,CAAC;IACtE,eAAe,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;IACjC,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,mIAAmI;AACnI,SAAS,cAAc,CAAC,UAAkB,EAAE,WAAmB;IAC7D,OAAO,GAAG,UAAU,GAAG,WAAW,EAAE,CAAC;AACvC,CAAC;AAED;;;;;GAKG;AACH,SAAS,QAAQ,CACf,GAAW,EACX,QAAgB,EAChB,GAAW,EACX,YAAoB;IAEpB,OAAO,GAAG,CAAC,CAAC,MAAM,EAAE,GAAG,GAAG,IAAI,YAAY,EAAE,CAAC,EAAE,QAAQ,CAAC,CAAC;AAC3D,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,SAAS,yBAAyB,CAChC,GAAkB,EAClB,GAAW,EACX,QAAgB,EAChB,OAAe,EACf,WAAgC;IAEhC,IAAI,GAAG,CAAC,UAAU,IAAI,gBAAgB,CAAC,GAAG,EAAE,GAAG,EAAE,QAAQ,EAAE,OAAO,CAAC,EAAE,CAAC;QACpE,OAAO,iBAAiB,CAAC,GAAG,CAAC,CAAC;IAChC,CAAC;IACD,OAAO,WAAW,EAAE,CAAC;AACvB,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,SAAS,sBAAsB,CAC7B,GAAW,EACX,QAAgB,EAChB,cAAsB,EACtB,eAAwB,EACxB,UAA+B;IAE/B,qEAAqE;IACrE,MAAM,SAAS,GAAG,GAAG,CACnB,CAAC,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,cAAc,CAAC,EAC/C,QAAQ,CACT,CAAC;IACF,IAAI,SAAS,KAAK,IAAI;QAAE,OAAO,SAAS,CAAC;IACzC,IAAI,SAAS,KAAK,EAAE;QAAE,OAAO,WAAW,CAAC;IAEzC,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;IAC5B,IAAI,MAAM,KAAK,IAAI;QAAE,OAAO,SAAS,CAAC;IACtC,MAAM,SAAS,GAAG,QAAQ,CACxB,GAAG,EACH,QAAQ,EACR,MAAM,EACN,cAAc,CAAC,MAAM,EAAE,cAAc,CAAC,CACvC,CAAC;IACF,IAAI,SAAS,KAAK,IAAI;QAAE,OAAO,SAAS,CAAC;IACzC,MAAM,cAAc,GAAG,oBAAoB,CACzC,gBAAgB,CAAC,SAAS,CAAC,CAAC,WAAW,CAAC,MAAM,CAC/C,CAAC;IACF,OAAO,cAAc,KAAK,eAAe,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,eAAe,CAAC;AAC5E,CAAC;AAED,SAAS,UAAU,CAAC,YAAoB;IACtC,OAAO,IAAI,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC;AACrD,CAAC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,mBAAmB,GAAG,IAAI,OAAO,EAGpC,CAAC;AAEJ,SAAS,uBAAuB,CAC9B,GAAkB,EAClB,GAAW,EACX,QAAgB,EAChB,cAAsB;IAEtB,OAAO,yBAAyB,CAAC,GAAG,EAAE,GAAG,EAAE,QAAQ,EAAE,cAAc,EAAE,GAAG,EAAE;QACxE,IAAI,KAAK,GAAG,mBAAmB,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACzC,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,KAAK,GAAG,IAAI,GAAG,EAAE,CAAC;YAClB,mBAAmB,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QACtC,CAAC;QACD,MAAM,MAAM,GAAG,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;QACzC,IAAI,MAAM,KAAK,SAAS;YAAE,OAAO,MAAM,CAAC;QACxC,MAAM,KAAK,GAAG,kBAAkB,CAAC,GAAG,EAAE,QAAQ,EAAE,cAAc,CAAC,CAAC;QAChE,KAAK,CAAC,GAAG,CAAC,cAAc,EAAE,KAAK,CAAC,CAAC;QACjC,OAAO,KAAK,CAAC;IACf,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,gBAAgB,GAAG,IAAI,OAAO,EAA0B,CAAC;AAE/D,SAAS,mBAAmB,CAC1B,GAAkB,EAClB,GAAW,EACX,QAAgB;IAEhB,MAAM,MAAM,GAAG,gBAAgB,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACzC,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,MAAM,CAAC;IACxC,MAAM,GAAG,GAAG,GAAG,CAAC,CAAC,WAAW,EAAE,yBAAyB,CAAC,EAAE,QAAQ,CAAC,CAAC;IACpE,MAAM,SAAS,GAAG,GAAG,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,MAAM,CAAC;IAC9D,gBAAgB,CAAC,GAAG,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;IACrC,OAAO,SAAS,CAAC;AACnB,CAAC;AAkBD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmDG;AACH,SAAS,wBAAwB,CAC/B,GAAW,EACX,QAAgB,EAChB,cAAsB,EACtB,aAA4B,EAC5B,UAA+B;IAE/B,2EAA2E;IAC3E,yEAAyE;IACzE,2EAA2E;IAC3E,yEAAyE;IACzE,yEAAyE;IACzE,yEAAyE;IACzE,MAAM,IAAI,GAAG,GAAG,CACd,CAAC,KAAK,EAAE,IAAI,EAAE,iBAAiB,EAAE,IAAI,EAAE,cAAc,CAAC,EACtD,QAAQ,CACT,CAAC;IACF,IAAI,IAAI,KAAK,IAAI;QAAE,OAAO,SAAS,CAAC;IACpC,MAAM,CAAC,GAAG,EAAE,UAAU,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC3C,IAAI,CAAC,GAAG;QAAE,OAAO,SAAS,CAAC;IAC3B,MAAM,OAAO,GAAG,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC;IACtE,oEAAoE;IACpE,wEAAwE;IACxE,uEAAuE;IACvE,oEAAoE;IACpE,uEAAuE;IACvE,2EAA2E;IAC3E,kEAAkE;IAClE,yEAAyE;IACzE,gEAAgE;IAChE,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,aAAa,EAAE,CAAC,CAAC,CAAC,sBAAsB,CAAC,CAAC,CAAC,WAAW,CAAC;IAChE,CAAC;IACD,MAAM,WAAW,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IAE/B,yEAAyE;IACzE,0EAA0E;IAC1E,+CAA+C;IAC/C,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;IAC5B,IAAI,MAAM,KAAK,IAAI;QAAE,OAAO,SAAS,CAAC;IACtC,MAAM,eAAe,GAAG,cAAc,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IAE/D,MAAM,QAAQ,GAAG,cAAc,CAC7B,GAAG,EACH,QAAQ,EACR,WAAW,EACX,GAAG,EACH,eAAe,CAChB,CAAC;IACF,IAAI,QAAQ,CAAC,IAAI,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IAClD,IAAI,QAAQ,CAAC,IAAI,KAAK,SAAS;QAAE,OAAO,WAAW,CAAC;IAEpD,MAAM,OAAO,GAAG,QAAQ,CAAC,GAAG,EAAE,QAAQ,EAAE,GAAG,EAAE,eAAe,CAAC,CAAC;IAC9D,IAAI,OAAO,KAAK,IAAI;QAAE,OAAO,SAAS,CAAC;IACvC,MAAM,MAAM,GAAG,QAAQ,CAAC,GAAG,EAAE,QAAQ,EAAE,WAAW,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC;IACnE,IAAI,MAAM,KAAK,IAAI;QAAE,OAAO,SAAS,CAAC;IAEtC,MAAM,YAAY,GAAG,oBAAoB,CACvC,gBAAgB,CAAC,OAAO,CAAC,CAAC,WAAW,CAAC,MAAM,CAC7C,CAAC;IACF,MAAM,WAAW,GAAG,oBAAoB,CACtC,gBAAgB,CAAC,MAAM,CAAC,CAAC,WAAW,CAAC,MAAM,CAC5C,CAAC;IACF,0EAA0E;IAC1E,oEAAoE;IACpE,OAAO,YAAY,KAAK,WAAW,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,eAAe,CAAC;AACtE,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,SAAS,cAAc,CACrB,GAAW,EACX,QAAgB,EAChB,SAAiB,EACjB,GAAW,EACX,eAAuB;IAKvB,MAAM,UAAU,GAAG,GAAG,CACpB;QACE,WAAW;QACX,IAAI;QACJ,IAAI;QACJ,IAAI;QACJ,eAAe;QACf,gBAAgB;QAChB,SAAS;QACT,GAAG;KACJ,EACD,QAAQ,CACT,CAAC;IACF,IAAI,UAAU,KAAK,IAAI;QAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;IAEpD,wEAAwE;IACxE,wEAAwE;IACxE,wEAAwE;IACxE,uEAAuE;IACvE,sEAAsE;IACtE,yEAAyE;IACzE,uEAAuE;IACvE,yEAAyE;IACzE,gDAAgD;IAChD,MAAM,MAAM,GAAG,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC;IAC9D,IAAI,CAAC,GAAG,CAAC,CAAC;IACV,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC;QACzB,MAAM,MAAM,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;QACzB,IAAI,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YACrD,MAAM,OAAO,GAAG,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;YAC9B,MAAM,OAAO,GAAG,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;YAC9B,CAAC,IAAI,CAAC,CAAC;YACP,IAAI,OAAO,KAAK,eAAe,EAAE,CAAC;gBAChC,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;YAC5C,CAAC;YACD,SAAS;QACX,CAAC;QACD,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QACxB,CAAC,IAAI,CAAC,CAAC;QACP,IAAI,CAAC,KAAK,eAAe;YAAE,SAAS;QACpC,IAAI,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;QACvD,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,eAAe,EAAE,CAAC;IACjD,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,eAAe,EAAE,CAAC;AACjD,CAAC"}
package/dist/types.d.ts CHANGED
@@ -79,6 +79,33 @@ export interface BundleContext {
79
79
  * was not passed, in which case the rule applies its own default.
80
80
  */
81
81
  freshnessFutureSkewSeconds?: number;
82
+ /**
83
+ * Opt-in for `sources-fresh` AND `sources-fresh-future` (see
84
+ * `--dirty-as-now` in `src/cli.ts`): every uncommitted change (modified,
85
+ * staged, or untracked per `git status --porcelain --untracked-files=all`) is modeled as though
86
+ * it landed in ONE virtual commit made right now, instead of at its last
87
+ * real commit's time. That virtual-commit epoch is the SINGLE place both
88
+ * rules read a dirty path's "commit time" from (see
89
+ * `getDocCommitEpochShared`/`commitEpochFor` in
90
+ * `src/rules/sources-fresh.ts`): a dirty `sources` path's epoch becomes
91
+ * now (`sources-fresh`'s base comparison), and a dirty DOC's own epoch
92
+ * becomes now too (both `sources-fresh`'s co-commit rescue and
93
+ * `sources-fresh-future`'s timestamp comparison read that same virtual
94
+ * epoch). Closes the gap between a pre-commit `check` run (which sees
95
+ * only committed history) and CI's post-commit run (which sees the new
96
+ * commit time) for content edited but not yet committed. Undefined (the
97
+ * default) leaves both rules looking only at committed history,
98
+ * byte-identical to behavior before this option existed.
99
+ */
100
+ dirtyAsNow?: boolean;
101
+ /**
102
+ * Clock for `--dirty-as-now`'s virtual-commit instant, in milliseconds
103
+ * since the epoch like `Date.now` (the default when undefined). Read
104
+ * exactly ONCE per `check` run (`getNowEpochShared` in
105
+ * `src/rules/sources-fresh.ts`); injectable so a test can pin that count
106
+ * and the shared value rather than racing the wall clock.
107
+ */
108
+ now?: () => number;
82
109
  }
83
110
  export interface Rule {
84
111
  id: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "okf-kit",
3
- "version": "0.10.0",
3
+ "version": "0.12.0",
4
4
  "description": "CLI that validates OKF v0.1 knowledge bundles for structural correctness",
5
5
  "type": "module",
6
6
  "bin": {