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
5 changes: 5 additions & 0 deletions .agents/skills/netscript-harness/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,11 @@ intent hint, not the final profile.
11. **Run IMPL-EVAL (separate session).**
12. Close: update `context-pack.md`, `arch-debt.md`, and promote lessons if warranted.

When the opening diff is docs-only, proactively apply `ci:skip-e2e` to the draft PR and also apply
`ci:skip-scaffold` when scaffold-static is not applicable. Use `ci:full` as the escape hatch when
the docs-only change must exercise the full CI surface. Record the selection in the opening phase
comment so the evaluator can verify that the cheap lane is intentional.

## Agent Delegation Contract

Lane assignments and model bindings are configuration. The tiered A–E model, the selection rules
Expand Down
10 changes: 7 additions & 3 deletions .agents/skills/netscript-pr/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,8 @@ a board column reflect reality.
- `area:` — `cli`, `fresh`, `fresh-ui`, `plugins`, `auth`, `deps`, `aspire`, `tooling`, `database`,
`kv`, `sdk`, `service`, `config`, `telemetry`, `ai-core`, `plugin-ai`, `docs`
- `priority:` — `p0` (release blocker), `p1`, `p2`, `p3`
- `ci:` — `skip-e2e`, `full` (manual overrides for the path-filtered CI); `gate:` — `e2e`, `jsr`
- `ci:` — `skip-e2e`, `skip-scaffold`, `full` (manual overrides for the path-filtered CI);
`gate:` — `e2e`, `jsr`
- `epic:` — groups every issue/PR belonging to a program epic (e.g. `epic:ai-stack`,
`epic:deployment`); the epic's own umbrella issue carries `type:umbrella`.
- `wave:` — scheduling band that drives the **milestone** (see below): `v1`, `v1-min`, `defer`.
Expand Down Expand Up @@ -307,5 +308,8 @@ human-facing mirror; `.github/labels.yml` is the machine-readable label set.

## Path-filter awareness (Phase C)

Config/docs-only PRs are auto-skipped by CI `paths-ignore`; override with `ci:full` when a docs PR
must run e2e, or `ci:skip-e2e` to suppress it. Don't fight the filter by editing workflows per-PR.
When opening a docs-only PR, proactively apply `ci:skip-e2e`. Also apply `ci:skip-scaffold` when the
change does not need the scaffold-static gate. These labels make the intended cheap lane explicit
even when path classification would auto-skip the jobs. Apply `ci:full` instead when a docs-only PR
must exercise the full CI surface; it is the precedence-winning escape hatch. Don't fight the
filter by editing workflows per PR.
5 changes: 5 additions & 0 deletions .claude/skills/netscript-harness/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,11 @@ intent hint, not the final profile.
11. **Run IMPL-EVAL (separate session).**
12. Close: update `context-pack.md`, `arch-debt.md`, and promote lessons if warranted.

When the opening diff is docs-only, proactively apply `ci:skip-e2e` to the draft PR and also apply
`ci:skip-scaffold` when scaffold-static is not applicable. Use `ci:full` as the escape hatch when
the docs-only change must exercise the full CI surface. Record the selection in the opening phase
comment so the evaluator can verify that the cheap lane is intentional.

## Agent Delegation Contract

Lane assignments and model bindings are configuration. The tiered A–E model, the selection rules
Expand Down
10 changes: 7 additions & 3 deletions .claude/skills/netscript-pr/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,8 @@ a board column reflect reality.
- `area:` — `cli`, `fresh`, `fresh-ui`, `plugins`, `auth`, `deps`, `aspire`, `tooling`, `database`,
`kv`, `sdk`, `service`, `config`, `telemetry`, `ai-core`, `plugin-ai`, `docs`
- `priority:` — `p0` (release blocker), `p1`, `p2`, `p3`
- `ci:` — `skip-e2e`, `full` (manual overrides for the path-filtered CI); `gate:` — `e2e`, `jsr`
- `ci:` — `skip-e2e`, `skip-scaffold`, `full` (manual overrides for the path-filtered CI);
`gate:` — `e2e`, `jsr`
- `epic:` — groups every issue/PR belonging to a program epic (e.g. `epic:ai-stack`,
`epic:deployment`); the epic's own umbrella issue carries `type:umbrella`.
- `wave:` — scheduling band that drives the **milestone** (see below): `v1`, `v1-min`, `defer`.
Expand Down Expand Up @@ -307,5 +308,8 @@ human-facing mirror; `.github/labels.yml` is the machine-readable label set.

## Path-filter awareness (Phase C)

Config/docs-only PRs are auto-skipped by CI `paths-ignore`; override with `ci:full` when a docs PR
must run e2e, or `ci:skip-e2e` to suppress it. Don't fight the filter by editing workflows per-PR.
When opening a docs-only PR, proactively apply `ci:skip-e2e`. Also apply `ci:skip-scaffold` when the
change does not need the scaffold-static gate. These labels make the intended cheap lane explicit
even when path classification would auto-skip the jobs. Apply `ci:full` instead when a docs-only PR
must exercise the full CI surface; it is the precedence-winning escape hatch. Don't fight the
filter by editing workflows per PR.
56 changes: 51 additions & 5 deletions .github/scripts/ci-classify-changes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,16 @@ Deno.test('parseNameStatus: docs-to-docs rename stays docs-only', () => {
assertEquals(d.runRuntime, false);
});

Deno.test('rename of a Markdown file under packages stays docs-only', () => {
const files = parseNameStatus(
'R100\tpackages/cli/OLD_README.md\tpackages/cli/README.md',
);
const d = decide({ eventName: 'pull_request', files, labels: [] });
assertEquals(d.docsOnly, true);
assertEquals(d.runStatic, false);
assertEquals(d.runRuntime, false);
});

Deno.test('parseNameStatus: unrecognisable line degrades to a bare path (forces run)', () => {
const files = parseNameStatus('something-weird-without-tab');
assertEquals(files, ['something-weird-without-tab']);
Expand Down Expand Up @@ -105,11 +115,14 @@ Deno.test('docs surfaces are docs-only', () => {
}
});

Deno.test('denylist wins over the markdown allowlist', () => {
// Markdown under an impacting prefix must NOT be docs-only.
assertEquals(isDocsOnlyPath('packages/cli/README.md'), false);
assertEquals(isDocsOnlyPath('plugins/workers/CHANGELOG.md'), false);
assertEquals(isDocsOnlyPath('apps/demo/README.md'), false);
Deno.test('Markdown is docs-only under ordinary impacting directories', () => {
assertEquals(isDocsOnlyPath('packages/cli/README.md'), true);
assertEquals(isDocsOnlyPath('plugins/workers/CHANGELOG.md'), true);
assertEquals(isDocsOnlyPath('apps/demo/README.mdx'), true);
});

Deno.test('critical workflow paths win over the markdown allowlist', () => {
assertEquals(isDocsOnlyPath('.github/workflows/README.md'), false);
});

Deno.test('impacting surfaces force the gate', () => {
Expand Down Expand Up @@ -148,6 +161,39 @@ Deno.test('decide: docs-only PR skips both jobs', () => {
assertEquals(d.runRuntime, false);
});

Deno.test('decide: Markdown-only diff under packages is docs-only', () => {
const d = decide({
eventName: 'pull_request',
files: ['packages/cli/README.md', 'packages/sdk/guides/setup.mdx'],
labels: [],
});
assertEquals(d.docsOnly, true);
assertEquals(d.runStatic, false);
assertEquals(d.runRuntime, false);
});

Deno.test('decide: Markdown plus one TypeScript file is full', () => {
const d = decide({
eventName: 'pull_request',
files: ['packages/cli/README.md', 'packages/cli/mod.ts'],
labels: [],
});
assertEquals(d.docsOnly, false);
assertEquals(d.runStatic, true);
assertEquals(d.runRuntime, true);
});

Deno.test('decide: Markdown plus deno.lock is full', () => {
const d = decide({
eventName: 'pull_request',
files: ['packages/cli/README.md', 'deno.lock'],
labels: [],
});
assertEquals(d.docsOnly, false);
assertEquals(d.runStatic, true);
assertEquals(d.runRuntime, true);
});

Deno.test('decide: one code file forces both jobs', () => {
const d = decide({
eventName: 'pull_request',
Expand Down
26 changes: 17 additions & 9 deletions .github/scripts/ci-classify-changes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,10 @@
*
* Design contract (see PR "ci: docs-only + label-gated skip lanes"):
*
* - A file is "docs-only" (non-impacting for the scaffold/e2e gate) iff it
* matches the docs allowlist AND does NOT match the impacting denylist.
* - The denylist ALWAYS wins: anything under `packages/`, `plugins/`, `apps/`,
* any `deno.json*` / `deno.lock`, or `.github/workflows/**` is treated as
* impacting even if it is a Markdown file. This honours the hard rule that
* those surfaces must never be classified docs-only.
* - Markdown / MDX is docs-only regardless of directory, except for explicit
* critical paths (`deno.json*`, `deno.lock`, workflows), which always win.
* - Non-Markdown paths under `packages/`, `plugins/`, and `apps/` remain
* impacting; known documentation/agent-context directories remain allowed.
* - `docsOnly` is true only when there is at least one changed file and every
* changed file is docs-only.
*
Expand Down Expand Up @@ -57,7 +55,7 @@ const DOCS_PREFIXES = [
'.claude/',
] as const;

/** Extensions that are docs-only wherever they live (subject to the denylist). */
/** Extensions that are docs-only wherever they live (subject to critical paths). */
const DOCS_EXTENSIONS = ['.md', '.mdx'] as const;

/** Normalise a git path: strip a leading `./`, collapse backslashes. */
Expand All @@ -77,6 +75,14 @@ export function isImpacting(rawPath: string): boolean {
return IMPACTING_PREFIXES.some((prefix) => path.startsWith(prefix));
}

/** Does this explicit configuration/workflow path always force the gate? */
function isCritical(rawPath: string): boolean {
const path = normalise(rawPath);
const base = path.slice(path.lastIndexOf('/') + 1);
return path.startsWith('.github/workflows/') ||
base === 'deno.json' || base === 'deno.jsonc' || base === 'deno.lock';
}

/** Is this path a docs-only surface (only meaningful when not impacting)? */
export function isDocs(rawPath: string): boolean {
const path = normalise(rawPath);
Expand All @@ -86,10 +92,12 @@ export function isDocs(rawPath: string): boolean {
}

/**
* True iff `path` is docs-only: matches the docs allowlist and is not on the
* impacting denylist (denylist wins).
* True iff `path` is docs-only. Explicit critical paths win; Markdown / MDX
* then wins over ordinary impacting directory prefixes.
*/
export function isDocsOnlyPath(path: string): boolean {
if (isCritical(path)) return false;
if (DOCS_EXTENSIONS.some((ext) => normalise(path).endsWith(ext))) return true;
if (isImpacting(path)) return false;
return isDocs(path);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Context Pack: #611 CI Markdown-only classifier

## Run Metadata

| Field | Value |
| --- | --- |
| Run ID | `fix-611-ci-docs-only-md--ci-classifier` |
| Branch | `fix/611-ci-docs-only-md` |
| Current phase | `close` |
| Archetype | `N/A` |
| Scope overlays | `docs` |

## Current State

Research, locked plan, Design checkpoint, and separate-session PLAN-EVAL are complete against `origin/main` `720fcb7e`; implementation is unlocked.

## Completed

- Loaded harness, PR, tools, docs overlay, gate matrix, and PLAN-EVAL protocol.
- Inspected classifier, tests, source skills, issue #611, branch, and clean worktree.
- Separate Claude Opus PLAN-EVAL returned `PASS` in `plan-eval.md`.
- Classifier slice implemented with all 30 focused tests and scoped check/format green.
- Skill guidance landed in source skills and Claude mirrors were regenerated/check-verified.
- Separate Claude Opus IMPL-EVAL returned `PASS`; issue #611 close-gate acceptance is fully evidenced.

## In Progress

- Refresh PR body/status and hand off ready-to-merge draft PR.

## Next Steps

1. Commit/push evaluator trail, refresh PR body, and transition canonical status.

## Key Decisions

| Decision | Source | Notes |
| --- | --- | --- |
| Global Markdown/MDX precedence with explicit critical overrides | issue #611 / plan D1 | Covers package/plugin/app README-only diffs. |
| Preserve rename/copy dual-path parsing | issue #611 / plan D2 | Prevents source rename holes. |

## Files Changed

| Path | Status | Notes |
| --- | --- | --- |
| `.github/scripts/ci-classify-changes.ts` | changed | Critical-path guard precedes global Markdown/MDX classification. |
| `.github/scripts/ci-classify-changes.test.ts` | changed | Requested mixed/lock/package/rename regressions. |
| `.agents/skills/netscript-pr/SKILL.md` | changed | Proactive docs-only CI label guidance and taxonomy. |
| `.agents/skills/netscript-harness/SKILL.md` | changed | Opening-session label guidance. |
| `.claude/skills/netscript-{pr,harness}/SKILL.md` | generated | Synced from authoritative skills. |

## Gates

| Gate family | Current status | Evidence |
| --- | --- | --- |
| Static | PASS | 30 tests; scoped check and format checks green. |
| Fitness | N/A | No package/plugin surface. |
| Runtime | N/A | Pure classifier tests cover behavior. |
| Consumer | PASS | Claude mirror sync/check: 17 skills, 21 files. |

## Drift and Debt

- Drift: beta milestone mapping recorded in `drift.md`.
- Debt: none.

## Commits

- See draft PR commit list and per-slice comments.
21 changes: 21 additions & 0 deletions .llm/runs/fix-611-ci-docs-only-md--ci-classifier/drift.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Drift Log: #611 CI Markdown-only classifier

## 2026-07-11 — milestone mapping follows current repository roadmap

- **What:** PR #613 uses milestone `0.0.1-beta.7`.
- **Source:** live milestone list queried during PR bootstrap.
- **Expected:** the PR skill names `0.0.1-beta.1` as its beta milestone example/target.
- **Actual:** `0.0.1-beta.1` is absent; current open beta milestones start later, and #611 was filed during beta.6 wrap-up, so the next beta is `0.0.1-beta.7`.
- **Severity:** minor
- **Action:** accept
- **Evidence:** PR #613 milestone and `gh api repos/rickylabs/netscript/milestones`.

## 2026-07-11 — requested status label is not in the repository taxonomy

- **What:** The brief requests `status:in-progress`, but the namespaced repository taxonomy contains no such label.
- **Source:** `.github/labels.yml` and `.agents/skills/netscript-pr/SKILL.md`.
- **Expected:** PR label `status:in-progress`.
- **Actual:** The canonical harness lifecycle uses `status:impl` during implementation, `status:impl-eval` during evaluation, and `status:ready-merge` after IMPL-EVAL PASS.
- **Severity:** minor
- **Action:** accept; follow the canonical single-status lifecycle rather than inventing a new label.
- **Evidence:** PR #613 phase comments/label history and `evaluate.md` PASS.
Loading
Loading