Skip to content
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
b443255
chore(deps): bump DavidAnson/markdownlint-cli2-action
dependabot[bot] Aug 10, 2026
c15ff8c
chore(deps): bump pydantic-settings from 2.14.2 to 2.15.0
dependabot[bot] Aug 10, 2026
b900798
chore(deps): bump uvicorn from 0.52.0 to 0.52.1
dependabot[bot] Aug 10, 2026
61b8368
chore(deps-dev): bump graphifyy from 0.9.32 to 0.9.35
dependabot[bot] Aug 10, 2026
2127c71
docs: plan issue #2153 — replace plan/history files with GitHub issue…
ahouseholder Aug 10, 2026
d4ad48d
Merge pull request #2154 from CERTCC/dependabot/github_actions/DavidA…
sei-ahouseholder Aug 10, 2026
8b9097a
Merge pull request #2155 from CERTCC/dependabot/uv/pydantic-settings-…
sei-ahouseholder Aug 10, 2026
3406628
Merge pull request #2156 from CERTCC/dependabot/uv/uvicorn-0.52.1
sei-ahouseholder Aug 10, 2026
00eac61
Merge pull request #2157 from CERTCC/dependabot/uv/graphifyy-0.9.35
sei-ahouseholder Aug 10, 2026
fcdfbe8
Merge pull request #2158 from CERTCC/plan/2153-replace-history-files-…
sei-ahouseholder Aug 10, 2026
922eaba
history: archive idea IDEA-2153 — Replace plan/history files with Git…
ahouseholder Aug 10, 2026
915cd07
Merge pull request #2161 from CERTCC/chore/archive-idea-2153
sei-ahouseholder Aug 10, 2026
3a67839
feat: implement GitHub comment output mode in append-history (HM-08)
ahouseholder Aug 10, 2026
cd3ac5d
Merge pull request #2162 from CERTCC/task/2159-implement-github-comme…
sei-ahouseholder Aug 10, 2026
b11fe3d
docs: plan issue #1898 — SE-07 phrase slot coverage
ahouseholder Aug 10, 2026
d981f0a
history: archive learning CONCERN-1898 — SE-07 phrase slot-coverage —…
ahouseholder Aug 10, 2026
d553aec
fix(demo): gate invite-path RM triage on Finder replica in 4 scenarios
ahouseholder Aug 10, 2026
fdb4112
history: archive implementation ISSUE-2141 — gate invite-path RM tria…
ahouseholder Aug 10, 2026
71cd9c7
fix(pr-execute): address 2 findings from triage
ahouseholder Aug 10, 2026
1c9918a
merge: resolve conflict in notes/demo-ci-scenario-coverage.md
ahouseholder Aug 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 32 additions & 8 deletions .agents/skills/archive-history/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,38 +38,60 @@ The caller provides four pieces of information (see the calling skill for body f
### Step 1 — Pipe entry body to `append-history`

```bash
cat <<'ENDOFENTRY' | uv run append-history <TYPE> \
HISTORY_OUTPUT=$(cat <<'ENDOFENTRY' | uv run append-history <TYPE> \
--title "<TITLE>" \
--source "<SOURCE>"

<Full entry body — include PR URL, impl issue links, and outcome summary>

ENDOFENTRY
)
```

The tool writes `plan/history/YYMM/<type>/<source>.md` and regenerates
`plan/history/YYMM/README.md` locally (the README is gitignored).
Capture the output in `HISTORY_OUTPUT`. The tool either:

### Step 2 — Lint the new history files
- **File mode**: writes `plan/history/YYMM/<type>/<source>.md`, regenerates
the local `plan/history/YYMM/README.md` (gitignored), and prints the file
path to stdout.
- **GitHub comment mode** (`implementation`/`idea` with `--source ISSUE-N`):
posts a comment on the issue and prints the comment URL to stdout
(starts with `https://`). No file is written.

### Step 2 — Check output mode

```bash
if [[ "$HISTORY_OUTPUT" == https://* ]]; then
echo "Posted as GitHub comment: $HISTORY_OUTPUT"
# Skip Steps 3–5 (no file was written).
exit 0
fi
```

If the output is a URL, the entry was posted as a GitHub comment — skip
all `git` steps (HM-08-004). Record the URL for the caller's reference.

### Step 3 — Lint the new history files

Only reached when a file was written:

```bash
markdownlint-cli2 --fix --config .markdownlint-cli2.yaml \
"plan/history/$(date +%y%m)/**/*.md"
```

### Step 3 — Stage and commit
### Step 4 — Stage and commit

```bash
git add plan/history/
uv run git commit -m "history: archive <TYPE> <SOURCE> — <TITLE>
git commit -m "history: archive <TYPE> <SOURCE> — <TITLE>

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>"
```

The `Co-authored-by` trailer is **required** on this commit just as on all
others — the history commit is the most commonly missing it.

### Step 4 — Push
### Step 5 — Push

```bash
git push "https://x-access-token:$(gh auth token)@github.com/CERTCC/Vultron.git" HEAD
Expand All @@ -81,6 +103,8 @@ git push "https://x-access-token:$(gh auth token)@github.com/CERTCC/Vultron.git"

- **Always call after PR creation** — include the PR URL in the entry body.
- **One entry per invocation** — for multiple entries, call this skill in a loop.
- **Do not call `git push` separately** — this skill always pushes as its final step.
- **Skip git steps when output is a URL** — GitHub comment mode writes no file;
`git add`, commit, and push are not needed (HM-08-004, HM-08-006).
- **Do not call `git push` separately** — this skill always pushes as its final step (file mode only).
- **Do not amend** — open a new commit via a fresh invocation rather than amending.
- History files are **immutable** once pushed.
2 changes: 1 addition & 1 deletion .github/workflows/lint_md_all.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: DavidAnson/markdownlint-cli2-action@6bf21b07787794f89a243495939cd651942aeabe # v24.1.0
- uses: DavidAnson/markdownlint-cli2-action@21c1be1b93ad9ed58fa840aacc3f279cde2a72ff # v24.2.0
with:
globs: "**/*.md"
config: .markdownlint-cli2.yaml
7 changes: 7 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -539,6 +539,13 @@ See [notes/agents-md-structure.md](notes/agents-md-structure.md) for routing pol
is green. Racing parallel PRs to `main` means each PR can only confirm its own
scenario passes — none can confirm it hasn't perturbed other currently-passing
scenarios. *Source: CONCERN-2137*
- **`SemanticEntry` Phrases MUST Use Only `{actor}`, `{object}`, `{target}`** —
the runtime render pipeline (`CaseTimelineEvent.summary`, `event_phrase()`)
never fills `{context}`, `{origin}`, or `{inner_object}`. A phrase referencing
one of those slots passes the `defaultdict`-based SE-07-004 test (which fills
every slot with `"X"`) but produces a dangling `"—"` in production. The
allowlist test (SE-07-005 in `test/test_semantic_registry.py`) enforces this
structurally. *Source: CONCERN-1898*

---

Expand Down
55 changes: 51 additions & 4 deletions notes/history-management.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,15 @@ tool and the migration from monolithic `plan/*HISTORY.md` files.
| Question | Decision | Rationale |
|---|---|---|
| Which history files are in scope? | All `*HISTORY.md` files, including any future additions | Uniform treatment avoids per-file special cases |
| Where do history files live? | `plan/history/` subdirectory | Separates archive from active planning; prevents agents from reading history during orientation |
| Chunking granularity? | Monthly (YYMM suffix in directory name) | Recent history is most relevant; monthly chunks keep the current month manageable |
| File structure per entry? | Individual write-once files: `plan/history/YYMM/<type>/<entry-id>.md` | Clean git diffs (new file added, not existing file edited); enables YAML frontmatter per entry |
| Where do history files live? | `plan/history/` subdirectory for `learning`/`priority`; GitHub issue comments for `implementation`/`idea` when source resolves to an issue number | Co-locates completion narrative with the original problem statement; eliminates file bloat (~100–200 files/month) |
| Chunking granularity? | Monthly (YYMM suffix in directory name) for file-mode entries | Recent history is most relevant; monthly chunks keep the current month manageable |
| File structure per entry? | Individual write-once files: `plan/history/YYMM/<type>/<entry-id>.md` (file mode only) | Clean git diffs (new file added, not existing file edited); enables YAML frontmatter per entry |
| GitHub comment mode? | `append-history implementation` / `append-history idea` with `--source ISSUE-N` posts a comment on issue N; falls back to file mode for non-ISSUE-N source values | Allows gradual migration; pre-issue-number legacy sources continue writing files |
| Month-level navigation? | Auto-generated `plan/history/YYMM/README.md` rebuilt by the tool on every append; **gitignored** — use `uv run show-history [--month YYMM \| --all]` to view | Eliminates PR merge conflicts caused by parallel branches each regenerating the same file |
| Top-level static README? | Yes — `plan/history/README.md` explains legacy files and the transition date | Documents the migration boundary for future readers |
| Legacy file migration? | Move monolithic files to `plan/history/` top level as static archives | Cannot be split retroactively without manual effort; grandfathered content preserved |
| Existing chunked files (pre-Aug 2026)? | Leave as-is — immutable historical records (HM-01-005) | Backfilling ~800 files would require significant API volume; pre-Aug 2026 files are infrequently accessed |
| Aug 2026+ backfill? | Separate issue — post each 2608+ implementation/idea file as a comment, then remove the file | Scoped to recent history where co-location benefit is highest; tracked as a distinct task |
| Tool interface? | `uv run append-history <type>` — `--title` and `--source` required; body via stdin or `--file <path>` | Frontmatter is built by the tool; agents provide only body content and named params |
| Date determination? | Use current system clock (UTC) by default; allow a backfill-only override for migration tooling | Normal agents avoid month-selection decisions, while legacy backfill still needs historical placement |
| Timestamp field? | `timestamp: datetime (UTC ISO 8601)` replaces legacy `date: date`; legacy entries are converted automatically by model validator | Enables sub-day ordering and reliable sorting in README tables |
Expand All @@ -43,7 +46,7 @@ tool and the migration from monolithic `plan/*HISTORY.md` files.
| Type validation? | `HistoryEntryType` StrEnum in `vultron/metadata/history/types.py` | Adding a new type requires only one line change |
| Module location? | `vultron/metadata/history/` — sibling of `vultron/metadata/specs/` | Both are project-management metadata tools; co-location signals intent |
| Agent context boundary? | `plan/history/` is explicitly excluded from default "read plan context" | Prevents agents from spending context tokens on historical archive during orientation |
| Skill updates? | `build`, `ingest-idea`, `learn` skills must use `append-history` for writes | Skills that directly append to history files must be updated to use the tool |
| Skill updates? | `build`, `ingest-idea`, `learn` skills must use `append-history` for writes; `archive-history` must skip commit/push when output is a GitHub comment | Skills that directly append to history files must be updated to use the tool |

---

Expand Down Expand Up @@ -95,6 +98,50 @@ source: IDEA-26042702

---

## GitHub Comment Output Mode

For `implementation` and `idea` entry types, when `--source` resolves to a GitHub
issue number (`ISSUE-N` or bare integer N), `append-history` posts the entry body
as a comment on that issue rather than writing a file. This co-locates the
completion narrative with the original problem statement.

### Source resolution rules

| `--source` value | Output |
|---|---|
| `ISSUE-2153` | Comment on issue #2153 |
| `2153` (bare integer) | Comment on issue #2153 |
| `IDEA-26042702` | File at `plan/history/YYMM/idea/IDEA-26042702.md` |
| `TASK-BTND5` | File at `plan/history/YYMM/implementation/TASK-BTND5.md` |

`learning` and `priority` types always write files regardless of source format.

### Skill behaviour change

When `append-history` posts a GitHub comment it prints the comment URL to stdout
(not a file path). The `archive-history` skill MUST detect this and skip the
`git add plan/history/` and commit/push steps — there is no new file to stage.

### Comment format

```markdown
**History: implementation — Fix demo config cache leak**

<full entry body text>
```

The heading line uses the entry type and `--title` value. The body follows
unchanged from what would have been the file-based Markdown body.

### Backfill of existing files

Files written before August 2026 are left as-is (HM-01-005 immutability rule).
A separate backfill task covers the 2608 directory and forward: for each
`implementation`/`idea` file whose filename is `ISSUE-N.md`, post the body as a
comment on issue N and delete the file.

---

## `append-history` Tool

### Location
Expand Down
28 changes: 28 additions & 0 deletions plan/history/2608/idea/IDEA-2153.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
---
source: IDEA-2153
timestamp: '2026-08-10T20:24:19.527277+00:00'
title: Replace plan/history files with GitHub issue completion comments
type: idea
---

## IDEA-2153 — Replace plan/history files with GitHub issue completion comments

Observed that `plan/history/` was accumulating 100–200 committed files per
month (160+ in 2607, 140+ in 2606), with completion narratives split from the
GitHub issues they describe. Sampled 10 recent entries from 2608 against their
corresponding GitHub issues on 2026-08-10 and confirmed the pattern: issue
bodies hold the problem/requirements; history files hold the completion story.

**Decision**: For `implementation` and `idea` entry types whose `--source`
resolves to a GitHub issue number (`ISSUE-N` or bare integer), `append-history`
posts the entry body as a comment on that GitHub issue instead of writing a
file. `learning` and `priority` types remain file-backed. Source values that
don't resolve to an issue number (e.g., `IDEA-26042702`, `TASK-BTND5`) fall
back to the file-based path unchanged.

**Processed**: 2026-08-10 — implementation tracked in #2159 (tool + skill
update) and #2160 (2608+ backfill).

Docs PR: <https://github.com/CERTCC/Vultron/pull/2158>
Spec: `specs/history-management.yaml` HM-08 (v1.4.0).
Notes: `notes/history-management.md` § "GitHub Comment Output Mode".
30 changes: 30 additions & 0 deletions plan/history/2608/implementation/ISSUE-2141.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
---
source: ISSUE-2141
timestamp: '2026-08-10T21:10:09.426979+00:00'
title: gate invite-path RM triage on Finder replica in 4 demo scenarios
type: implementation
---

Issue: #2141 — Bug: 4 full-suite demo scenarios have unguarded invite-path CLP-08-005

Symptoms: fccv-handoff, fvcv-extension, fccv-extension, fvv demos intermittently
fail with ReconstructChainTailNode raising an unanchored-chain error. Root cause:
the Finder receives Announce(CaseLedgerEntry) broadcast by run_invite_path_rm_triage
before its genesis VulnerabilityCase hash is seeded (CLP-08-005 race).

Root cause: invite-path RM triage triggers a ledger entry announcement to all
participants including the Finder. If the Finder's genesis VulnerabilityCase
hasn't been replicated yet, the chain tail reconstruction fails.

Fix: Added wait_for_case_on_container(finder_client, case.id_) immediately before
run_invite_path_rm_triage in 4 phase functions:

- fccv_handoff_demo._phase_c2_invites_vendor (+ finder_client param added)
- fvcv_extension_demo._phase_coordinator_suggests_vendor2 (+ finder_client param added)
- fccv_extension_demo._phase_c2_suggests_vendor (+ finder_client param added)
- fvv_demo._phase_report_submission (finder_client already present)

Regression tests added to all 4 test files (TestFinderCaseReplicaWaitBefore*Triage),
verifying signature presence and wait-before-triage ordering.

PR: <https://github.com/CERTCC/Vultron/pull/2165>
42 changes: 42 additions & 0 deletions plan/history/2608/learning/CONCERN-1898.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
---
source: CONCERN-1898
timestamp: '2026-08-10T19:33:05.608167+00:00'
title: SE-07 phrase slot-coverage — defaultdict masks unfillable slots
type: learning
---

## What Was Learned

The SE-07 parametrized tests used `defaultdict(lambda: "X")` to fill all
phrase slots, which masked a class of bug: a `SemanticEntry` phrase referencing
`{context}`, `{origin}`, or `{inner_object}` would pass the test but produce a
dangling `"—"` fallback at render time because the runtime pipeline
(`CaseTimelineEvent.summary`, `event_phrase()`) never populates those three
slots.

The fix is two-layer:

1. **Structural allowlist test** (`test_no_phrase_uses_unpopulated_slots` in
`test/test_semantic_registry.py`) — fails if any phrase references a slot
outside `{actor}`, `{object}`, `{target}`. Module-level constants
`_RUNTIME_POPULATED_SLOTS` and `_RESERVED_UNPOPULATED_SLOTS` document the
split.

2. **Behavioural render tests** (`TestEventPhraseBehavioural`,
`TestSummarySlotsFilledBehavioural` in `test/demo/test_report.py`) — call
`event_phrase()` and `CaseTimelineEvent.summary` with real event-type values
and assert no trailing `"—"` and no un-substituted `{slot}` markers remain.

## Spec Impact

- `specs/semantic-extraction.yaml`: SE-07-002 annotated with runtime-population
note; SE-07-005 (structural allowlist MUST) and SE-07-006 (behavioural render
MUST) added.
- `AGENTS.md`: pitfall entry added — phrases MUST use only `{actor}`,
`{object}`, `{target}`.

## Outcome

Docs PR: <https://github.com/CERTCC/Vultron/pull/2149>
Implementation issue: #2150 (build: implement SE-07-005/006 render-pipeline
slot coverage tests; blocked-by #1898, child of epic #1937)
6 changes: 3 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,14 @@ dependencies = [
"py-trees>=2.5.0",
"rdflib>=7.2.1",
"scipy>=1.18.0",
"uvicorn>=0.52.0",
"uvicorn>=0.52.1",
"click>=8.4.2",
"griffelib>=2.1.0",
"transitions>=0.9.3",
"sqlmodel>=0.0.39",
"pyyaml>=6.0",
"python-frontmatter>=1.3.0",
"pydantic-settings>=2.14.2",
"pydantic-settings>=2.15.0",
"httpx2",
]
dynamic = ["version",]
Expand Down Expand Up @@ -150,7 +150,7 @@ profile = "black"
dev = [
"black>=26.5.1",
"flake8>=7.3.0",
"graphifyy>=0.9.32",
"graphifyy>=0.9.35",
"isort>=7.0.0",
"linkchecker>=10.6.0",
"mypy>=2.3.0",
Expand Down
Loading
Loading