Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion .cursor-plugin/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,6 @@
"anti-slop"
],
"license": "MIT",
"version": "4.8.2",
"version": "4.8.3",
"repository": "https://github.com/agent-kit-startup/agent-kit"
}
2 changes: 1 addition & 1 deletion .cursor/agent-kit.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"schemaVersion": 1,
"version": "4.8.2",
"version": "4.8.3",
"protected": [
".cursor/HANDOFF.md",
".cursor/agents/test-suites.md",
Expand Down
9 changes: 5 additions & 4 deletions .cursor/commands/git-staging.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ Follow the **git staging** routine to bring local changes to the pre-production

1. **Read** the "Prompt: git staging" section in `autogit/gitupdate.md` (when it exists).
2. **Staging hygiene (monitors):** if `git status` shows untracked or unrelated dirty `.cursor/memory/plan-monitor-*.md`, **warn** before commit. Stage memory/monitor files **add-by-name only**; never broad `git add` of `.cursor/memory/` WIP into a product commit (ADR `decisions/2026-07-27_plan-monitor-consumer-awareness.md`, external-review staging hygiene).
3. Run in order: validation (not on `main`), CHANGELOG (`[Unreleased]`), checkout staging, pull, working branch, Conventional Commits, push, MR/PR, merge, cleanup.
4. **Never** commit directly to `main`.
5. On completion: update `.cursor/HANDOFF.md` (phase in staging); memory-loop WRITE if it applies.
6. Optional: update the project's PM tool (ClickUp, Jira, ...) if MCP is configured.
3. **Lint evidence (required when code/format paths change):** before claiming staging-ready, **run** the repo formatter/linter on touched files and **record the command + result** (pass/fail) in the worker summary or tick notes. Writing `Staging ready: yes` or the contract string alone is **not** evidence. Pure markdown / docs-only with no applicable linter: state `none applicable`. Same gate as `/run-plan` Staging-ready lint gate.
4. Run in order: validation (not on `main`), CHANGELOG (`[Unreleased]`), checkout staging, pull, working branch, Conventional Commits, push, MR/PR (**always `--base staging` / target `staging`**), merge, cleanup.
5. **Never** commit directly to `main`.
6. On completion: update `.cursor/HANDOFF.md` (phase in staging); memory-loop WRITE if it applies.
7. Optional: update the project's PM tool (ClickUp, Jira, ...) if MCP is configured.
23 changes: 23 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,34 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and

## [Unreleased]

## [4.8.3] - 2026-07-29

### Fixed

- Guard shell: strip surrounding quotes in push refspec normalize so `git push origin 'main'` and `"+main"` deny
- Biome format: compact quiet-cap / classifier parity asserts in `plugin-ux-validation.test.ts` after PR #496
- Flight Log quiet-cap: drop hardcoded fallback `5`; use SoT field only (missing → 0) and widen UX assertion (residual E)
- Flight Log classifier parity: scope shared-literal asserts to `classifyFlightLogMessageKind` / `flightLogMessageKind` bodies so `normalizeHandoffGaps` copies cannot satisfy them (residual F)
- Secrets scan: mask `json-secret-kv` (`"apiKey": "…"`) values in excerpts the same way as env assignments
- Guard shell: normalize push refspecs (`+` / `refs/heads/`) before protected-branch deny so `git push origin +main` and `refs/heads/main` are blocked
- Flight Log: shared `isFlightLogQuiet(d)` / `resolveFlightLogCurrent(d)` so fingerprint and renderer agree when Plan is none and Gaps come from handoff fallback
- Biome format: split two statements on one line in `plugin-ux-validation.test.ts` (unblocks `pnpm lint` / tag CI)
- Session-start hook: tests for readiness parsing + HANDOFF excerpt assembly (`buildSessionStartAdditionalContext`)
- `/git-prod` pre-tag gate requires `pnpm typecheck` + `pnpm test`; §12.5 documents post-tag `main` exception, scoped Path C smoke, and npm-publish-checklist gains durable cross-lens matrix + partial-row convention
- Doctor hooks health: adapters must exist, be executable, and CLI must resolve; guard shell covers `HEAD --` / `checkout .` / bare push on protected branch; secrets scan masks excerpts (hook omits raw spans); hand-install chmod +x in install.md + bootstrap
- Broad Intake Write-residuals invariant pinned in external-reports doc contract; Flight Log classifier parity covers all shared regex groups; classify pre-truncation (F4)
- Flight Log quiet open-triages: gate `flightLogFingerprint` on quiet Gaps+Warnings; derive cap/kind from SoT; build quiet lane from external reports (not starved attention)
- `TRIAGE_HEADING_RE` / `isReportTriaged`: match only durable headings (`Triage note` / `Follow-up plan` / `Residuals plan`); stop treating tick headings that name `triage-*` to-do ids as triaged (shared SoT in `dashboard/lib/triage-heading.mjs` + CLI parity)
- Path C Mission Control on macOS: escape `@` / `$` in Perl detach-start paths so `node_modules/@dadado/agent-kit-cli/dashboard/serve.mjs` is not stripped by array interpolation (`escapePerlDoubleQuoted`)
- Public sync content guard: avoid denylist/secret-pattern false positives in CLI test fixtures (`field-report-prompts` path; split GitHub PAT sample) so `sync-public` can advance the storefront after npm 4.8.2
- Public sync push protection: split Stripe live-key sample in `secrets-scan.test.ts` so GitHub secret scanning does not reject the sync PR branch

### Changed

- Close audit residuals A–E (`close-audit-residuals-ae`): staging lint-evidence process (#499); quoted push deny (#498); C deferred (quiet helpers still in `dashboard.html`); D–E accepted on monitor Closed-by; monitor + `_index` staged add-by-name (R14)
- `/git-staging` + `autogit/gitupdate.md`: staging-ready requires recorded lint command + result (not the contract string alone); GitHub PRs must use `--base staging`
- Memory Audits index: document curated (not exhaustive) policy for `_index.md` plan-monitor rows (ADR R14/R15 §3); stage five-monitor audit with index row (R14)

## [4.8.2] - 2026-07-29

### Fixed
Expand Down
10 changes: 7 additions & 3 deletions autogit/gitupdate.md
Original file line number Diff line number Diff line change
Expand Up @@ -275,10 +275,11 @@ This section contains the detailed prompts that should be followed when commands
- Make the requested changes (including CHANGELOG.md update if necessary).
- Review with `git status -sb` to ensure only expected files were modified.
- **Validation**: Confirm there's no attempt to modify `origin/main` directly.
- **Lint evidence (staging-ready):** when the diff touches formatted/linted paths (e.g. `*.ts` / `*.tsx` / `*.js` / `*.mjs` under `packages/`, `dashboard/`, or other Biome/ESLint scopes), **run** the focused linter on those files (e.g. `pnpm exec biome check <paths>`) **before** commit and **record the exact command + pass/fail output** in the tick / worker summary. Claiming `Staging ready: yes` or pasting the contract phrase without that recorded run is invalid. Pure markdown / docs-only with no applicable repo linter: record `Tests: none applicable` (or `Validation: none applicable`). Aligns with `/run-plan` Staging-ready lint gate (background: Biome-red merges fixed only after the fact).

#### 7. **Stage and commit with semantic message**
- Add relevant files with `git add` **by name**. If `git status` shows untracked or unrelated dirty `.cursor/memory/plan-monitor-*.md`, **warn** and do **not** broad-`git add` `.cursor/memory/` WIP into a product commit (ADR `decisions/2026-07-27_plan-monitor-consumer-awareness.md`).
- **Monitor closeout (R14):** when a tick intentionally stages a `plan-monitor-*.md` (and/or `_index.md` Audits row), add those paths **by name**. Prefer a separate docs/memory commit when the same PR also has large product diffs. Never sweep unrelated monitor WIP. ADR: `decisions/2026-07-29_plan-monitor-staging-hygiene-r14-r15.md`.
- **Monitor closeout (R14):** when a tick intentionally stages a `plan-monitor-*.md` (and/or `_index.md` Audits row), add those paths **by name**. Prefer a separate docs/memory commit when the same PR also has large product diffs. Never sweep unrelated monitor WIP. **An `_index.md` Audits row and its target monitor file must land in the same commit** (no index link without the file). ADR: `decisions/2026-07-29_plan-monitor-staging-hygiene-r14-r15.md`.
- Create a commit following [Conventional Commits](https://www.conventionalcommits.org/):
- `feat:` for new features
- `fix:` for bug fixes
Expand All @@ -293,7 +294,7 @@ This section contains the detailed prompts that should be followed when commands

#### 9. **Open and merge Merge Request / Pull Request**
- **GitLab:** Create the MR with `glab mr create --title "<title>" --description "<description>" --target-branch staging`. Then run `glab mr merge <number>` to merge. If it fails due to authentication, provide the manual creation link and await instructions.
- **GitHub:** Create the PR with `gh pr create --title "<title>" --body "<description>" --base staging`. Then run `gh pr merge <number>` (or the returned number). If it fails due to authentication, provide the manual creation link and await instructions.
- **GitHub:** Create the PR with `gh pr create --title "<title>" --body "<description>" --base staging`. **Always pass `--base staging`** (default base is often `main`; never merge staging work straight to `main`). Then run `gh pr merge <number>` (or the returned number). If it fails due to authentication, provide the manual creation link and await instructions.

#### 10. **Cleanup and final update**
- Run `git checkout staging` to return to staging branch (needed before deleting working branch).
Expand Down Expand Up @@ -335,7 +336,7 @@ This section contains the detailed prompts that should be followed when commands
- `.cursor/agent-kit.json`
- `.cursor-plugin/plugin.json`
Do not ship with only root+CLI bumped; L0 version-parity tests fail and tag CI skips publish/sync.
4. Prefer running focused L0 version-parity (`vitest` on `packages/cli/src/lifecycle/l0.test.ts`) or `pnpm test` on staging **before** the first tag push.
4. **Required before the first `v*` tag push for this SemVer:** on staging (or the commit about to become `main`), run `pnpm typecheck` and `pnpm test` (or at least focused L0 version-parity: `vitest` on `packages/cli/src/lifecycle/l0.test.ts` **plus** `pnpm typecheck`). Do not treat this as optional: tag CI that fails typecheck skips `publish-npm` / `sync-public` (see `errors/2026-07-29_tag-ci-typecheck-blocked-481-publish.md`).
5. Commit this change to working branch / staging **before** merging to `main` (via MR if necessary).
- If Unreleased is already empty and today's release reflects what's in staging, still verify all four manifests match the latest closed CHANGELOG version; bump and commit if they do not.

Expand Down Expand Up @@ -425,6 +426,9 @@ This section contains the detailed prompts that should be followed when commands
| Public sync PR **merged** | `sync-public` may open a PR; do **not** pass this row on CI-green alone. Confirm the public sync PR is **merged** (`gh pr view` / `gh pr list -R <public> --state merged`) before claiming public `main` is current |
| Public `main` | Latest commit message like `chore: sync private vX.Y.Z (...)` on the public default branch **after** that merge |
| Public GitHub Release | `gh release list -R <public>` shows `vX.Y.Z` as Latest (not a stale older release) |
| Scoped-install Path C smoke (manual or CI) | Install `@dadado/agent-kit-cli@X.Y.Z` into a blank folder under `node_modules/@dadado/…` (no kit checkout) and confirm `agent-kit dashboard` reaches HTTP 200 on loopback; required after Path C / detach-start changes |

**Post-tag `main` commits:** After a `vX.Y.Z` tag ships, CI-unblock or fixture commits may land on `main` while manifests still say `X.Y.Z`. That is allowed only when documented in the promote notes / HANDOFF (tag and npm tarball describe the tagged commit, not necessarily later `main`). Realign with the next SemVer when product fixes (for example Path C) must reach npm; never force-move the existing `v*` tag.

If `sync-public` failed, the sync PR is still open, or the public Release is missing: fix or re-run (`pnpm git:trigger-public-sync`), do not assume success from a green local merge/push or from tag CI alone. Write a memory/dogfood note when the gap was silent (npm green, public storefront stale; or CI green, sync PR unmerged).

Expand Down
117 changes: 95 additions & 22 deletions dashboard/dashboard.html
Original file line number Diff line number Diff line change
Expand Up @@ -5582,7 +5582,7 @@
/**
* Quiet-state open-triage row (untriaged external review). Prompt chrome;
* per-row Copy triage + path only. No Review all / Resolve all.
* Cap mirrors FLIGHT_LOG_QUIET_OPEN_TRIAGES_CAP in semantic-model.mjs.
* Kind class from flightLogKindClassName('prompt') (SoT map; not a literal).
* @param {{ id?: string, label?: string, sourcePath?: string, action?: { target?: string, label?: string, subject?: string } }} item
* @param {number} idx
*/
Expand All @@ -5603,8 +5603,9 @@
const copyTriageHandler = copyForPasteHandler(triageCmd, 'triage command', 'chatInput');
const copyPathHandler = copyRepoPathHandler(sourcePath);
const aria = `Review: ${label}`;
const kindClass = flightLogKindClassName('prompt');
return `
<div class="flight-log-card flight-log-card-current flight-log-kind-advice" role="listitem" tabindex="0" data-focus-key="flight-log-open-triage-${idx}" data-flight-log-kind="prompt" aria-label="${escapeAttr(aria)}">
<div class="flight-log-card flight-log-card-current ${kindClass}" role="listitem" tabindex="0" data-focus-key="flight-log-open-triage-${idx}" data-flight-log-kind="prompt" aria-label="${escapeAttr(aria)}">
<span class="flight-log-card-label">Review</span>
<div class="flight-log-card-text">${escapeHtml(label)}</div>
<div class="flight-log-card-meta">${escapeHtml(sourcePath)}</div>
Expand All @@ -5616,6 +5617,43 @@
`;
}

/**
* Resolve Live Gaps text for Flight Log render + fingerprint (same fallback chain).
* Prefer fl.current; with Plan:none, fall back to now.gaps then system.handoff.gaps.
*/
function resolveFlightLogCurrent(d) {
const fl = d?.missionControl?.flightLog ?? d?.flightLog ?? null;
if (typeof fl?.current === 'string' && fl.current.trim()) return fl.current.trim();
const nowGaps = d?.missionControl?.now?.gaps ?? d?.now?.gaps;
if (typeof nowGaps === 'string' && nowGaps.trim()) return nowGaps.trim();
const handoffGaps = d?.system?.handoff?.gaps;
if (typeof handoffGaps === 'string' && handoffGaps.trim()) return handoffGaps.trim();
return null;
}

function flightLogHasPastEntries(fl) {
if (!fl || !Array.isArray(fl.past)) return false;
return fl.past.some((e) => {
if (typeof e === 'string') return Boolean(e.trim());
return Boolean(e && typeof e.text === 'string' && e.text.trim());
});
}

/** Match renderFlightLogWarningCard: text-less warnings do not count. */
function flightLogHasWarningEntries(fl) {
if (!fl || !Array.isArray(fl.warnings)) return false;
return fl.warnings.some((w) => {
if (typeof w === 'string') return Boolean(w.trim());
return Boolean(w && typeof w.text === 'string' && w.text.trim());
});
}

/** Shared quiet gate for renderAttentionPanel + flightLogFingerprint. */
function isFlightLogQuiet(d) {
const fl = d?.missionControl?.flightLog ?? d?.flightLog ?? null;
return !resolveFlightLogCurrent(d) && !flightLogHasPastEntries(fl) && !flightLogHasWarningEntries(fl);
}

/**
* Flight Log panel: HANDOFF Gaps log (live large + earlier smaller) + Warnings lane.
* When Gaps + Warnings are empty, may show bounded quiet open-triage rows.
Expand All @@ -5626,22 +5664,22 @@
const sourcePath =
(typeof fl?.sourcePath === 'string' && fl.sourcePath.trim()) ||
'.cursor/HANDOFF.md';
const current =
typeof fl?.current === 'string' && fl.current.trim()
? fl.current.trim()
: typeof d.missionControl?.now?.gaps === 'string' && d.missionControl.now.gaps.trim()
? d.missionControl.now.gaps.trim()
: typeof d.system?.handoff?.gaps === 'string' && d.system.handoff.gaps.trim()
? d.system.handoff.gaps.trim()
: null;
const current = resolveFlightLogCurrent(d);
const past = Array.isArray(fl?.past) ? fl.past : [];
const warnings = Array.isArray(fl?.warnings) ? fl.warnings : [];
const quietCap =
typeof d.missionControl?.flightLogQuietOpenTriagesCap === 'number' &&
d.missionControl.flightLogQuietOpenTriagesCap > 0
? Math.floor(d.missionControl.flightLogQuietOpenTriagesCap)
: 0;
const quietOpenTriages = Array.isArray(fl?.quietOpenTriages)
? fl.quietOpenTriages
: Array.isArray(d.missionControl?.attention)
? d.missionControl.attention.filter(
(i) => i && i.kind === 'report' && typeof i.sourcePath === 'string' && i.sourcePath.trim(),
).slice(0, 5)
? d.missionControl.attention
.filter(
(i) => i && i.kind === 'report' && typeof i.sourcePath === 'string' && i.sourcePath.trim(),
)
.slice(0, quietCap)
: [];
const pastCards = past
.filter((e) => e && typeof e.text === 'string' && e.text.trim())
Expand Down Expand Up @@ -5675,6 +5713,7 @@
const hasCurrent = Boolean(current);
const hasPast = pastCards.length > 0;
const hasWarnings = warningCards.length > 0;
// Keep local flags aligned with isFlightLogQuiet(d) for the quiet branch.
if (!hasCurrent && !hasPast && !hasWarnings) {
if (hasOpenTriages) {
body = `<div class="flight-log-stack" role="list" aria-label="Reviews awaiting triage">${openTriageCards}</div>`;
Expand Down Expand Up @@ -5780,18 +5819,48 @@
return `Previous step elapsed ${formatElapsedPlain(row.elapsedMs)}`;
}

function flightLogFingerprint(fl) {
/**
* Fingerprint Flight Log for SSE re-render. Accepts full dashboard `d` or a
* slim prev snapshot `{ flightLog, now, system }` so quiet-gate matches render.
*/
function flightLogFingerprint(d) {
if (!d || typeof d !== 'object') return '';
const fl = d.missionControl?.flightLog ?? d.flightLog ?? null;
if (!fl || typeof fl !== 'object') return '';
const current = typeof fl.current === 'string' ? fl.current : '';
const current = resolveFlightLogCurrent(d) || '';
const past = Array.isArray(fl.past)
? fl.past.map((e) => (e && typeof e.text === 'string' ? e.text : '')).join('\n')
? fl.past
.map((e) =>
typeof e === 'string' ? e : e && typeof e.text === 'string' ? e.text : '',
)
.join('\n')
: '';
const warnings = Array.isArray(fl.warnings)
? fl.warnings.map((w) => (w && (w.id || w.text) ? String(w.id || w.text) : '')).join('\n')
: '';
const openTriages = Array.isArray(fl.quietOpenTriages)
? fl.quietOpenTriages.map((i) => (i && (i.id || i.sourcePath) ? String(i.id || i.sourcePath) : '')).join('\n')
? fl.warnings
.map((w) =>
typeof w === 'string'
? w
: w && (w.id || w.text)
? String(w.id || w.text)
: '',
)
.join('\n')
: '';
// Quiet-only: include open-triage ids when Gaps + Warnings are empty so a new
// monitor does not flash the non-quiet Flight Log card with no visible change.
// Must use the same gate as renderAttentionPanel (Plan:none Gaps fallback).
const openTriages =
isFlightLogQuiet(d) && Array.isArray(fl.quietOpenTriages)
? fl.quietOpenTriages
.map((i) =>
typeof i === 'string'
? i
: i && (i.id || i.sourcePath)
? String(i.id || i.sourcePath)
: '',
)
.join('\n')
: '';
return `${current}\0${past}\0${warnings}\0${openTriages}`;
}

Expand Down Expand Up @@ -6085,8 +6154,12 @@
const nowChanged = prevData && nowFingerprint(d.missionControl?.now) !== nowFingerprint(prevData.now);
const attentionChanged =
prevData &&
flightLogFingerprint(d.missionControl?.flightLog) !==
flightLogFingerprint(prevData.flightLog);
flightLogFingerprint(d) !==
flightLogFingerprint({
flightLog: prevData.flightLog,
now: prevData.now,
system: prevData.system,
});
prevData = snapshotPrevData(d);

dataLoading = false;
Expand Down
Loading