Skip to content

feat: add semantic versioning, CI release automation and the override model - #31

Open
ldastey-dev wants to merge 20 commits into
mainfrom
feat/versioning-and-override-system
Open

ldastey-dev wants to merge 20 commits into
mainfrom
feat/versioning-and-override-system

Conversation

@ldastey-dev

@ldastey-dev ldastey-dev commented Sep 6, 2026 •

Copy link
Copy Markdown
Owner

What this does

Makes a deployed copy of this library stay current without a merge, and makes divergence structurally impossible rather than merely detectable.

The problem

Deploying was a one-way copy. The moment a consumer edited anything, the deployment was stuck: a redeploy would either overwrite their work or refuse to touch it, and nothing could distinguish a deliberate customisation from an untouched file. Every deployment drifted and could never be safely refreshed.

The design

The deployed tree is split into a disposable base and a consumer-owned override layer.

Layer Owner On update
.context/{standards,playbooks,conventions,index.md} Library Deleted and recopied
.context/overrides/ Consumer Never touched
Managed block in AGENTS.md Library Rewritten in place; everything outside it is untouched

Because the base is disposable there is no three-way merge, no conflict resolution and no drift to reconcile. Updating is a delete-and-recopy.

Overrides declare mode: extend (library file loads, yours appends — you keep receiving improvements) or mode: replace (yours wins outright).

Cost

The staleness check is a single fetch of a six-byte VERSION file from a CDN-cached URL: no authentication, no rate limit, ~0.4s, and nothing meaningful added to an agent session. It fails open — an unreachable network reports and exits zero, so it can never block work. The unauthenticated REST API was rejected during design at 60 requests/hour per IP, which does not survive a fleet.

What is included

  • VERSION at 1.0.0, plus scripts/baselines/1.0.0.sha256 hashing main's content so migration can tell a pristine file from an edited one. Merging publishes 1.0.0 as the initial drop: with no earlier tag there is nothing to bump from, so the version is tagged as-is rather than skipping past it.
  • Three workflows — a Conventional Commit title check, a version gate that reports the exact version a merge will cut and rejects hand-edits to CI-owned files, and a release job that writes VERSION, CHANGELOG.md, the baseline, the tag and the GitHub release.
  • update.{sh,ps1} — --status / --check / --apply, pin-aware, fail-open.
  • migrate.{sh,ps1} — one-off upgrade for existing unversioned adopters that promotes their edits into the override layer before restoring the base.
  • scripts/ reorganisation and docs across README.md, MIGRATIONS.md and the maintainer AGENTS.md.

Design decisions worth reviewing

  • Only deployable content cuts a release. core/, standards/, playbooks/ and the deploy/update/migrate/lib scripts. README, workflows and tests reach no consumer, so they earn no version. The unconditional "every PR bumps" rule was dropped after research found no real-world project does this — every comparable project is path-conditional.
  • Commit type sets the bump size, never whether one happens. A deployable change is a release regardless of how its author labelled it; unrecognised types fall to a patch floor rather than blocking.
  • The commit is pushed before the tag is created. Tagging first yields a tag whose tree still holds the previous VERSION, so a consumer resolving that tag downloads content contradicting the version it was told to expect.
  • Consumers are pinned to their major line by default. A major requires deliberate action, which is what makes MIGRATIONS.md meaningful.
  • No third-party action in the release job. It holds contents: write; the changelog and baseline generators are a few dozen lines of portable shell instead.

Testing

Suite Result
bash end-to-end 81/81
PowerShell end-to-end 57/57
Pester 20/20
shellcheck --severity=warning clean
PSScriptAnalyzer (5.1 baseline) clean across all four shipped files

migrate.sh and migrate.ps1 were run against identical fixtures and their output trees diffed: byte-identical apart from the deployment timestamp. Fail-open behaviour was verified against the real network.

Before merging — action required

Branch protection has been removed, so the release job can push VERSION and the tag. If protection is reinstated it must be a ruleset with github-actions[bot] in bypass_actors — rulesets and classic protection are additive and most-restrictive-wins, so a classic rule left in place blocks the bot regardless of the ruleset.

Not tested end-to-end, because it cannot be until this merges and a second release exists: update.sh --apply against a real newer tag.

Please do not merge without reviewing. Merging this cuts 1.0.0 and tags v1.0.0.

ldastey-dev and others added 5 commits September 6, 2026 14:04
Relocates deploy.sh, deploy.ps1, deploy.Tests.ps1 and tests/ into a
scripts/ directory to keep the repository root clean, and adds VERSION
as the canonical version marker.

Content paths are resolved via a new SOURCE_ROOT/SourceRoot pointing at
the repository root, since the scripts now live one level down. Test
harnesses gain a separate SCRIPTS_DIR/ScriptsDir so REPO_DIR keeps
referring to the repository root for content lookups.

BREAKING CHANGE: deploy.sh and deploy.ps1 must now be invoked as
./scripts/deploy.sh and ./scripts/deploy.ps1.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Introduces the override architecture that makes divergence structurally
impossible instead of merely detectable.

Base content under .context/{standards,playbooks,conventions} is now
framework-owned and replaced wholesale on update. Consumers place changes
in .context/overrides/ mirroring the base path, with frontmatter declaring
mode: replace (ignore base) or mode: extend (layer over base). The
resolution rule is stated in .context/index.md and the AGENTS.md managed
block, costing roughly three lines of always-in-context budget.

AGENTS.md gains agentic-context:begin/end markers. Only that block is
rewritten on deploy or update, so [CONFIGURE] sections and any other
consumer content survive untouched.

Adds scripts/lib/common.sh with SemVer comparison (numeric, so 1.10.0
correctly exceeds 1.9.0), pin matching, fail-open network helpers and
manifest hashing; scripts/update.sh for staleness checks and in-place
updates; scripts/migrate.sh to upgrade pre-2.0 deployments by promoting
existing divergence into overrides automatically; and the 1.0.0 baseline
hashes that make that promotion possible.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds update.sh/update.ps1 for staleness checks against the upstream VERSION
and wholesale base refresh, and migrate.sh/migrate.ps1 to promote pre-2.0
local edits into the override layer.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds three workflows and the shared bump computation they both use, so the
version shown on a pull request is the version its merge cuts. VERSION,
CHANGELOG.md and scripts/baselines/ become CI-owned and hand-edits are
rejected. Corrects the 1.0.0 baseline to hash main rather than this branch.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…flow

Adds MIGRATIONS.md for the 1.x to 2.0.0 upgrade, README sections covering
customisation, versioning and staying current, and maintainer guidance on
deployable paths and CI-owned files. Extends both suites to cover the
manifest, override layer, managed block and version computation.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI lite review requested due to automatic review settings September 6, 2026 15:47
The version gate rejected the very commit that introduces VERSION and the
first baseline. It now rejects only changes to files that already exist on
the base, so creation is permitted once and every later edit is not.

Restores the executable bit lost when the scripts moved into scripts/, and
checks it in CI so the failure is not an opaque exit 126.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

It contains blocking workflow/tooling issues (bootstrap gating and pin preservation) and appears to include accidentally committed OOM report artefacts that should not ship.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This PR introduces a full “updateable deployment” model for this repository’s consumer installs by splitting deployed content into a disposable base layer plus a consumer-owned override layer, and adds semantic versioning + CI automation to publish releases, baselines, and upgrade tooling.

Changes:

  • Adds a manifest-driven override model (.context/overrides/) and a managed block in deployed AGENTS.md so updates can safely rewrite only framework-owned content.
  • Introduces consumer-facing update and migration tooling (update.*, migrate.*) plus shared SemVer/manifest helpers (scripts/lib/common.*).
  • Adds CI workflows and scripts to compute next versions, publish VERSION/baselines/changelog, and cut GitHub releases.
File summaries
File Description
VERSION Introduces canonical SemVer file at repo root.
scripts/update.sh Bash updater: status/check/apply update flow for deployed repos.
scripts/update.ps1 PowerShell updater equivalent to update.sh.
scripts/migrate.sh Bash migration tool for pre-override deployments.
scripts/migrate.ps1 PowerShell migration tool for pre-override deployments.
scripts/lib/common.sh Shared bash helpers (hashing, SemVer, network fetch, manifest helpers).
scripts/lib/common.ps1 Shared PowerShell helpers (hashing, SemVer, network fetch, manifest + managed block helpers).
scripts/deploy.sh Updates deploy to write manifest, scaffold overrides, install update tooling, and manage AGENTS.md block.
scripts/deploy.ps1 PowerShell deploy parity for manifest/overrides/update tooling + managed block.
scripts/ci/next-version.sh Computes next release version based on deployable-path changes + PR title subject.
scripts/ci/write-changelog.sh Generates/prepends changelog section during release.
scripts/ci/write-baseline.sh Writes per-release baseline hashes used by migration tooling.
scripts/baselines/1.0.0.sha256 Seeds baseline hashes for the initial “pre-override” version.
.github/workflows/pr-title.yml Enforces Conventional Commit PR titles to support deterministic version bumping.
.github/workflows/version-gate.yml PR gate to compute next version and reject edits to CI-owned artefacts.
.github/workflows/release.yml Release automation: writes VERSION/changelog/baseline, tags, and publishes GitHub release.
.github/workflows/deploy-sh-tests.yml Updates CI to run deploy e2e tests from new scripts/ location.
.github/workflows/deploy-ps1-tests.yml Updates CI to run PS deploy tests from new scripts/ location and cover shipped PS files.
scripts/tests/test-deploy.sh Expands bash e2e deploy tests for manifest/overrides/update status and manifest stability.
scripts/tests/test-deploy.ps1 Expands PowerShell e2e deploy tests for manifest/overrides/update status.
scripts/deploy.Tests.ps1 Expands Pester checks to include additional shipped PowerShell scripts.
scripts/tests/fixtures/assess-observability-skill.md Adds a fixture for skill wrapper generation testing.
README.md Documents new scripts layout, override model, versioning, and update/migration usage.
MIGRATIONS.md Adds consumer migration instructions for the 1.x → 2.0.0 override-model change.
core/AGENTS.md Adds managed-block markers + override/update guidance + extra configure sections to consumer template.
core/.context/index.md Adds override resolution instructions to the top of the context index.
core/.context/overrides/README.md Documents override authoring and resolution semantics (extend vs replace).
core/.context/overrides/standards/.gitkeep Scaffolds override standards directory in deployed targets.
core/.context/overrides/playbooks/.gitkeep Scaffolds override playbooks directory in deployed targets.
core/.context/overrides/conventions/.gitkeep Scaffolds override conventions directory in deployed targets.
AGENTS.md Updates maintainer guide to reflect scripts/ pathing and override/release model.
report.20260906.153947.85395.0.001.json Adds a Node OOM diagnostic report (likely accidental).
report.20260906.140740.74963.0.001.json Adds a second Node OOM diagnostic report (likely accidental).
Review details

Suppressed comments (2)

scripts/update.sh:206

  • Same issue as in --check: MIGRATIONS.md isn’t present in deployed repos, so this error message points to a file the user won’t have locally. Include an explicit URL to MIGRATIONS.md for the target version (or vendor MIGRATIONS.md into deployments).
    scripts/update.ps1:160
  • Same issue as in the check path: MIGRATIONS.md is not deployed to target repos, so this message points at a non-existent local file. Include an explicit URL (or deploy MIGRATIONS.md).
  • Files reviewed: 30/34 changed files
  • Comments generated: 10
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread .github/workflows/version-gate.yml Outdated
Comment thread .github/workflows/version-gate.yml Outdated
Comment thread report.20260906.140740.74963.0.001.json Outdated
Comment thread report.20260906.153947.85395.0.001.json Outdated
Comment thread scripts/migrate.sh Outdated
Comment thread scripts/update.sh
Comment thread scripts/lib/common.ps1
Comment thread scripts/update.sh
Comment thread scripts/update.ps1 Outdated
Comment thread scripts/update.sh Outdated
Copilot AI review requested due to automatic review settings September 6, 2026 15:53

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

It includes accidentally committed diagnostic report artifacts and has a few correctness/reliability issues in the update/release tooling (pin preservation, shell error handling, changelog duplication) that should be addressed before approval.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details

Suppressed comments (6)

scripts/update.sh:14

  • update.sh runs in --apply mode and performs destructive operations (rm/cp/tar), but the script is not running with set -e, so a failed command can be silently ignored and leave the deployment partially updated (and then still rewrite manifest.json). Enabling -e reduces the risk of ending up with a corrupted base tree.
    scripts/migrate.sh:15
  • migrate.sh --apply performs filesystem moves/copies that must either complete or fail loudly, but the script doesn't use set -e, so errors from cp, tar, sed, etc. may be missed and migration could complete with an inconsistent state. Using set -e makes the migration safer and more predictable.
set -uo pipefail

scripts/lib/common.ps1:235

  • Write-AcManifest always sets pin to <major(version)>.x, which means any consumer who edited the manifest to pin to * or an exact version will have their choice overwritten on every update.ps1 -Apply / migrate.ps1 -Apply. Consider adding an explicit -Pin parameter (defaulting to <major>.x) and passing through the existing manifest pin when present.
function Write-AcManifest {
    param(
        [Parameter(Mandatory)][string]$ContextDir,
        [Parameter(Mandatory)][string]$Version,
        [string[]]$Agents = @(),
        [string]$SourceRepo = $script:AcSourceRepo,
        [string]$CheckFrequency = 'weekly'
    )

    if (-not (Test-Path -LiteralPath $ContextDir)) {
        New-Item -ItemType Directory -Path $ContextDir -Force | Out-Null
    }

    $hashes = Get-AcContextHashes -ContextDir $ContextDir
    $files = [ordered]@{}
    foreach ($k in $hashes.Keys) { $files[$k] = $hashes[$k] }

    $manifest = [ordered]@{
        schema         = 1
        version        = $Version
        source         = $SourceRepo
        pin            = "$(Get-AcSemVerPart $Version 'Major').x"
        checkFrequency = $CheckFrequency
        deployedAt     = (Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ')
        agents         = @($Agents)
        files          = $files
    }

report.20260906.153947.85395.0.001.json:22

  • This appears to be a local Node/Copilot heap OOM diagnostic report and not part of the library, deploy tooling, or documentation. Keeping these in the repository will add noise and can bloat clones/CI artifacts; they should be removed from the PR and typically added to .gitignore (e.g. report.*.json).

{
  "header": {
    "reportVersion": 5,
    "event": "Allocation failed - JavaScript heap out of memory",
    "trigger": "OOMError",
    "filename": "report.20260906.153947.85395.0.001.json",
    "dumpEventTime": "2026-09-06T15:39:47Z",
    "dumpEventTimeStamp": "1788705587175",
    "processId": 85395,
    "threadId": null,
    "cwd": "/mnt/d/agentic-context",
    "commandLine": [
      "copilot",
      "--no-warnings",
      "--report-on-fatalerror",
      "--optimize-for-size",
      "--expose-gc",
      "copilot",
      "--resume"
    ],
    "nodejsVersion": "v24.20.0",

report.20260906.140740.74963.0.001.json:22

  • This looks like an accidentally committed local heap OOM report (not deployable content). Please remove it from the PR to avoid committing machine-specific diagnostics into the library repo, and consider ignoring report.*.json going forward.

{
  "header": {
    "reportVersion": 5,
    "event": "Allocation failed - JavaScript heap out of memory",
    "trigger": "OOMError",
    "filename": "report.20260906.140740.74963.0.001.json",
    "dumpEventTime": "2026-09-06T14:07:40Z",
    "dumpEventTimeStamp": "1788700060383",
    "processId": 74963,
    "threadId": 0,
    "cwd": "/mnt/d/agentic-context",
    "commandLine": [
      "copilot",
      "--no-warnings",
      "--report-on-fatalerror",
      "--optimize-for-size",
      "--expose-gc",
      "copilot"
    ],
    "nodejsVersion": "v24.20.0",
    "glibcVersionRuntime": "2.39",

scripts/update.sh:304

  • --apply rewrites manifest.json and currently hard-resets pin to <major(latest)>.x, overwriting any consumer pin they intentionally set (e.g. exact version or *). Since the updater is advertised as pin-aware, it should preserve an existing pin and only default it when absent.
  • Files reviewed: 30/34 changed files
  • Comments generated: 2
  • Review effort level: Lite

Comment thread scripts/ci/write-changelog.sh Outdated
Comment thread report.20260906.161839.89217.0.001.json Outdated
…st it

With no release tag there is nothing to bump from, so the version already in
VERSION is published as-is and tagged. Bumping here would have skipped 1.0.0
entirely and made the first tag disagree with the repository's own VERSION.

Both the gate and the release job now pass the latest tag explicitly, the
release job tolerates having nothing to commit on an initial release, and
both refuse to move a tag that already exists. Reframes migration as
unversioned to 1.0.0 rather than a 2.0.0 upgrade.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings September 6, 2026 16:53
@ldastey-dev ldastey-dev changed the title feat!: add semantic versioning, CI release automation and the override model feat: add semantic versioning, CI release automation and the override model Sep 6, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

It includes unintended diagnostic report artefacts and has update/deploy behaviours that currently undermine the stated pin/override ownership guarantees.

Review details

Suppressed comments (6)

Previously missed (1) — in code that hasn't changed since the last review.

scripts/update.ps1:266

  • update.ps1 -Apply reads the current manifest pin into $Pin, but then rewrites manifest.json via Write-AcManifest, which always sets pin to <major>.x (see scripts/lib/common.ps1). This drops any consumer-configured pin (e.g. * or an exact version) after every update. Preserve the existing pin when writing the new manifest (likely by extending Write-AcManifest to accept -Pin and passing $Pin here).

scripts/update.sh:304

  • update.sh --apply rewrites manifest.json with "pin": "<major>.x" every time, which discards any consumer-configured pin (e.g. * or an exact version) and makes pinning non-functional across updates. Use the existing PIN from the current manifest when present, and only default to <major>.x when it is empty.
    scripts/deploy.sh:768
  • The override layer is intended to be consumer-owned, but this deploy step will overwrite files under .context/overrides/ when --overwrite is used (e.g. a consumer-edited overrides/README.md). Avoid copying scaffold content into an existing overrides directory; only seed it on first deploy (and optionally create README.md only if missing).
    scripts/deploy.ps1:674
  • This deploy step can overwrite consumer-owned files under .context/overrides/ (especially when -Overwrite is used), contradicting the override model. Only copy the scaffold on first deploy; for existing overrides, avoid copying anything except maybe seeding README.md when it is missing.
    report.20260906.140740.74963.0.001.json:5
  • This looks like a local Node.js fatal OOM diagnostic report (includes command line, working directory, host name, environment variables, etc.) and is unlikely to be intended source content. It also adds a large amount of noise and potential sensitive machine details to the repo. Please remove this file from the commit and add it to .gitignore if it can be regenerated locally.

{
  "header": {
    "reportVersion": 5,
    "event": "Allocation failed - JavaScript heap out of memory",

report.20260906.153947.85395.0.001.json:5

  • This is a local Node.js fatal OOM diagnostic report and contains host/environment details; it also bloats the repository. Please remove it from the PR and ensure such crash reports are ignored going forward (e.g. via .gitignore patterns like report.*.json).

{
  "header": {
    "reportVersion": 5,
    "event": "Allocation failed - JavaScript heap out of memory",
  • Files reviewed: 30/34 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

Documents MAJOR, MINOR and PATCH in terms of their effect on a consumer's
deployment rather than the scale of the change, since that is the decision
a reader actually faces: whether an update can be applied unattended or
needs reading first. Adds the pinning defaults and how a version is derived.

Removes the bump derivation from the maintainer guide, which now links to
the README rather than restating it.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings September 6, 2026 17:03

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

The update/deploy/migrate scripts have portability and behavioural issues (BSD mktemp incompatibility and consumer pin preservation) and the PR includes accidental/sensitive crash report artefacts.

Review details

Suppressed comments (11)

Previously missed (6) — in code that hasn't changed since the last review.

scripts/deploy.sh:203

  • mktemp without a template fails on macOS/BSD mktemp, so managed-block replacement can break on a supported platform. Use a BSD-compatible mktemp -t fallback for temp files.
    scripts/migrate.sh:120
  • mktemp without a template fails on macOS/BSD mktemp, so the migration script will fail on a supported platform. Use a BSD-compatible mktemp -t fallback for temp files.

This issue also appears on line 243 of the same file.
scripts/update.ps1:266

  • Write-AcManifest always recomputes pin from the new version, which overwrites any consumer-chosen pin stored in the existing manifest (e.g. * or an exact version). That breaks pinning semantics across updates; consider preserving the prior pin on update, only defaulting it when missing.
    scripts/update.sh:214
  • mktemp -d without a template fails on macOS/BSD mktemp, so update.sh --apply will break on a supported platform. Use a BSD-compatible fallback when creating the temp directory.
    MIGRATIONS.md:38
  • The managed-block marker in deployed AGENTS.md includes the version (e.g. <!-- agentic-context:begin 1.0.0 -->), but this section documents a marker that doesn’t actually exist (<!-- agentic-context:begin -->). Updating the docs avoids confusing consumers who are trying to locate the block.
    core/.context/overrides/README.md:89
  • This claims update.sh warns when an override’s overrides: frontmatter doesn’t match its path, but the current update tooling only checks for orphaned targets (and does not validate this mismatch). Either implement the warning or adjust the docs to avoid promising behaviour that doesn’t exist.

scripts/update.sh:305

  • --apply rewrites manifest.json with pin forced to <major>.x, which overwrites any consumer-configured pin (e.g. * or an exact version) and breaks the advertised pinning behaviour. Preserve the existing pin value when present, only defaulting when it was missing.
    scripts/migrate.sh:243
  • mktemp without a template fails on macOS/BSD mktemp, so this temporary file creation is not portable as written.
    tmp="$(mktemp)"

scripts/lib/common.ps1:232

  • Write-AcManifest hard-codes pin to <major>.x, which means update flows overwrite any consumer-configured pin in an existing manifest. To support pinning as an owned consumer setting, Write-AcManifest should accept an optional Pin value and callers (especially update.ps1) should pass the existing pin through.
    $manifest = [ordered]@{
        schema         = 1
        version        = $Version
        source         = $SourceRepo
        pin            = "$(Get-AcSemVerPart $Version 'Major').x"
        checkFrequency = $CheckFrequency
        deployedAt     = (Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ')

report.20260906.153947.85395.0.001.json:6

  • This looks like a Node.js OOM crash report (likely generated by a local Copilot CLI run) and contains detailed environment/host/process information. It should not be committed to the repository; please remove it from the PR (and consider adding a gitignore rule to avoid reintroducing these reports).
{
  "header": {
    "reportVersion": 5,
    "event": "Allocation failed - JavaScript heap out of memory",
    "trigger": "OOMError",

report.20260906.140740.74963.0.001.json:6

  • This is another Node.js OOM crash report file with detailed local environment/process data. It should not be committed; please remove it from the PR (and optionally ignore report.*.json patterns).
{
  "header": {
    "reportVersion": 5,
    "event": "Allocation failed - JavaScript heap out of memory",
    "trigger": "OOMError",
  • Files reviewed: 30/34 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings September 6, 2026 17:28
@ldastey-dev
ldastey-dev force-pushed the feat/versioning-and-override-system branch from 0a4b7bc to f41aa5a Compare September 6, 2026 17:28

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The current deploy/update scripts can overwrite consumer-owned override scaffolding and do not preserve consumer-configured pin behaviour consistently, which can cause unexpected update semantics or clobber consumer edits.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details

Suppressed comments (5)

scripts/deploy.sh:670

  • Copying the whole core/.context directory into the target also copies the override scaffold (core/.context/overrides) into .context/overrides. On a redeploy with --overwrite this will overwrite consumer-owned files like .context/overrides/README.md, contradicting the contract that overrides are never touched once created.

This issue also appears on line 765 of the same file.
scripts/deploy.sh:768

  • The override layer is described as consumer-owned and should never be overwritten, but deploy.sh always recopies the override scaffold. With --overwrite this can overwrite an existing .context/overrides/README.md (or any future scaffold files), which is avoidable by only scaffolding on first deploy.
    scripts/update.sh:305
  • Applying an update rewrites manifest.json with a new pin based on the updated version's major (".x"), which silently discards any consumer-configured pin (e.g. "*" or an exact version). That breaks the documented pin semantics and can change future update behaviour unexpectedly.
    scripts/deploy.ps1:592
  • Copy-DirectoryContents of core/.context also copies core/.context/overrides into .context/overrides. On a redeploy with -Overwrite this will overwrite consumer-owned files like .context/overrides/README.md, contradicting the contract that overrides are never touched once created.

This issue also appears on line 668 of the same file.
scripts/deploy.ps1:675

  • The override layer is consumer-owned but deploy.ps1 recopies the override scaffold on every run, and Copy-DirectoryContents will overwrite existing files when -Overwrite is used. This can clobber .context/overrides/README.md (and any future scaffold files). Only scaffold the override layer when it does not already exist.
  • Files reviewed: 27/32 changed files
  • Comments generated: 3
  • Review effort level: Lite

Comment thread scripts/update.sh
Comment thread scripts/update.ps1
Comment thread MIGRATIONS.md Outdated
Three keyword routes in core/.context/index.md pointed at playbooks that do
not exist, so an agent following them resolved nothing:

  assess/compliance.md    -> split into assess/gdpr.md and assess/pci-dss.md
  assess/test-coverage.md -> renamed to assess/testing.md
  review/test-coverage.md -> renamed to review/testing.md

Four assess playbooks (ci-cd, cost-optimisation, operational-excellence and
resilience) had no route at all and were undiscoverable. All 29 standards and
45 playbooks are now routed, with no route pointing at a missing file.

Separate the two distinct meanings of a baseline. scripts/baselines/
<version>.sha256 records what a tagged release shipped and is written by CI.
The migration baseline records the pre-versioning content that unversioned
adopters actually deployed, and is now scripts/baselines/unversioned.sha256.

Previously both were the same file, so the release job would have regenerated
1.0.0.sha256 from the release tree. That is harmless today because migrate
compares only standards, playbooks and conventions, none of which differ on
this branch. It stops being harmless as soon as a release also edits a
standard or playbook: every such file would be reported as a consumer edit
and promoted to a mode: replace override, freezing consumers on stale
content. write-baseline.sh accepts only valid SemVer, so CI can never
overwrite the unversioned baseline.

Correct the README Repository Structure block, which had drifted: three
missing standards, and stale counts and file lists for assess, review and
setup.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings September 6, 2026 18:24

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The PR title workflow currently blocks unrecognised Conventional Commit types (contradicting the documented “patch-floor” behaviour), and the changelog generator will duplicate breaking-change entries across sections.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details

Suppressed comments (1)

scripts/ci/write-changelog.sh:67

  • Breaking-change commits (e.g. feat!: / fix!:) will be duplicated in the changelog: they are emitted once under "Breaking changes" and again under "Features"/"Fixes" because the group regexes also match subjects containing !:.
emit_group '^feat(\([^)]*\))?!?: ' 'Features'
emit_group '^fix(\([^)]*\))?!?: ' 'Fixes'
emit_group '^(docs|refactor|perf|style)(\([^)]*\))?!?: ' 'Other changes'
  • Files reviewed: 27/32 changed files
  • Comments generated: 1
  • Review effort level: Lite

Comment thread .github/workflows/pr-title.yml

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The bash updater’s apply path can silently continue after failed cp operations (no set -e), risking inconsistent deployments while still rewriting the manifest/VERSION.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details

Suppressed comments (2)

scripts/update.sh:294

  • In the --apply path, several critical copies (bin tools and shared libs) are not checked for failure. Because update.sh intentionally does not run with set -e, a failed cp here can leave .context/bin/* partially updated while the script continues, rewrites manifest.json and VERSION, and reports success. This also contradicts the comment above stating every operation is checked explicitly.
    scripts/lib/common.sh:195
  • ac_hash_context_tree is described as hashing “every deployable file”, but it currently hashes only *.md. Since scripts/deploy.sh copies all files under playbooks/ (including non-markdown companions like playbooks/setup/create-local-otel-stack/validate-config.sh, docker-compose.yaml, versions.env, etc.), those base files are not tracked in manifest.json and edits to them won’t be detected/reported by update/migrate workflows.

If the intent is to track only markdown context files, the comment should be tightened to avoid implying broader coverage; if the intent is to protect consumers from losing any base edits, the hash/baseline logic likely needs expanding in both bash and PowerShell to include non-markdown base files while still excluding manifest.json, VERSION, .last-update-check, overrides/, and bin/.

# Hash every deployable file in a deployed .context tree, printing
# "<relative-path>  <sha256>" lines sorted by path.
#
# Relative paths are relative to the .context directory. Overrides and the
# bin/ directory are excluded: overrides belong to the consumer, and bin/ is
# refreshed like any other base file but is not part of the content baseline.
ac_hash_context_tree() {
  local ctx="$1" f rel
  [ -d "$ctx" ] || return 1
  find "$ctx" -type f -name '*.md' \
    ! -path "$ctx/overrides/*" \
    ! -path "$ctx/bin/*" \
    2>/dev/null | LC_ALL=C sort | while IFS= read -r f; do
      rel="${f#"$ctx"/}"
      printf '%s  %s\n' "$rel" "$(ac_sha256 "$f")"
    done
  • Files reviewed: 29/34 changed files
  • Comments generated: 1
  • Review effort level: Lite

Comment thread scripts/tests/test-deploy.sh
Actions the two suppressed findings from the latest automated review.

update.sh left the bin tool and library copies unchecked in the apply path,
which directly contradicted the comment above them stating that every
operation there is checked. Because the script deliberately does not run under
set -e, a failed copy left .context/bin partially updated while the run
continued, rewrote manifest.json and VERSION, and reported success. All of
them now route through partial_apply like the rest of the apply path.

The manifest hashed only *.md while describing itself as covering every
deployable file. deploy ships non-markdown companions under playbooks/ -
compose files, collector configs, env files and shell scripts - and update
replaces each area wholesale, so a consumer edit to one was destroyed with no
divergence report. That is exactly the outcome the override layer exists to
prevent, and it was invisible precisely because those files were untracked.

Hashing now covers all base content in both languages, excluding overrides/
and bin/ as before, and additionally manifest.json, VERSION and
.last-update-check, which are generated state rather than content -
manifest.json cannot hash itself. Nine previously untracked files are now
covered, and the bash and PowerShell implementations were confirmed to produce
identical output over all 87 entries.

Editing a shipped docker-compose.yaml is now reported by update --status with
the override path to move it to, where previously it was silently overwritten.

ac_hash_source_tree is left markdown-only and its comment corrected to say so.
It backs the published baselines, migrate classifies non-markdown companions
by source presence rather than by hash, and the frozen unversioned baseline is
markdown-only, so widening it would make the baselines disagree.

Adds TC15 covering the tracking, the exclusions and the divergence report.
105 bash assertions, up from 100.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings September 6, 2026 21:16
@ldastey-dev

Copy link
Copy Markdown
Owner Author

Two further suppressed findings, both accepted and fixed in 8a75dab.

scripts/update.sh:294 — unchecked copies in the apply path

Correct, and it contradicted my own comment directly above the block claiming every operation there is checked. I had routed the area copies through partial_apply but missed the bin/ tools and libraries. Since the script deliberately does not run under set -e, a failed copy left .context/bin partially updated while the run continued, rewrote manifest.json and VERSION, and reported success. All of them now go through partial_apply.

scripts/lib/common.sh:195 — hashing described as covering every deployable file, but limited to *.md

Accepted, and I took the second of the two options offered — expanding coverage rather than narrowing the comment — because the narrow version leaves a real gap rather than merely a documentation inaccuracy.

deploy ships nine non-markdown companions under playbooks/setup/create-local-otel-stack/ (compose files, collector configs, versions.env, and both shell and PowerShell start scripts). update replaces each area wholesale, so a consumer edit to any of them was destroyed with no divergence report at all — the precise outcome the override layer exists to prevent, and invisible exactly because those files were untracked.

Hashing now covers all base content in both languages, excluding overrides/ and bin/ as before, plus manifest.json, VERSION and .last-update-check, which are generated state rather than content — manifest.json cannot hash itself. Verified the two implementations agree exactly:

total tracked: 87
non-markdown tracked: 9
EXCLUSION VIOLATIONS: []
bash vs PowerShell: IDENTICAL (87 entries)

And the behaviour that was missing now works:

Locally modified base files (these will be restored on update):
  - playbooks/setup/create-local-otel-stack/docker-compose.yaml
      move your change to .context/overrides/playbooks/setup/create-local-otel-stack/docker-compose.yaml

One part deliberately not widened. ac_hash_source_tree stays markdown-only, and I have corrected its comment instead. It backs the published baselines; migrate classifies non-markdown companions by whether they exist in the source tree rather than by hash; and scripts/baselines/unversioned.sha256 is frozen and markdown-only by construction. Widening it would make the baselines disagree with each other and with the frozen one, for no gain — the consumer-protection gap was in the manifest, and that is where it has been closed.

TC15 covers the tracking, all four exclusion classes and the divergence report. 105 bash assertions, up from 100.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

It introduces and rewires consumer-facing deploy/update/migrate tooling plus release automation, so a manual end-to-end validation pass is warranted before merge.

Review details

Suppressed comments (1)

scripts/tests/test-deploy.sh:6

  • The usage comment still references the old path (./tests/test-deploy.sh), but this script now lives under scripts/tests/. This makes the self-documentation misleading for anyone running the suite locally.
  • Files reviewed: 29/34 changed files
  • Comments generated: 1
  • Review effort level: Lite

Comment thread scripts/update.sh

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Redeploy currently overwrites consumer-owned manifest configuration (pin/checkFrequency), which can silently change update behaviour and should be preserved.

Review details

Suppressed comments (3)

scripts/deploy.sh:302

  • write_manifest() always rewrites manifest.json with a fresh "pin" and "checkFrequency", which will overwrite any consumer configuration on a redeploy (e.g. pin "*" or a non-weekly check frequency). update.sh/update.ps1 explicitly preserve these as consumer-owned config, so deploy should do the same when a manifest already exists.
    scripts/deploy.ps1:756
  • deploy.ps1 rewrites .context/manifest.json on every deploy without preserving any existing "pin" / "checkFrequency" values. This can silently reset a consumer’s pin (e.g. "*" to accept majors) on redeploy, changing update behaviour unexpectedly.
    scripts/tests/test-deploy.sh:6
  • The usage comment still references the old path (./tests/test-deploy.sh) even though this test suite now lives under scripts/tests/ and CI invokes it from there. This makes local invocation instructions incorrect.
  • Files reviewed: 29/34 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

Actions the three suppressed findings from the latest automated review.

pin and checkFrequency are consumer configuration, not derived state, but both
deploy scripts rewrote them unconditionally. Redeploying over an existing
deployment therefore reset a pin of "*" back to the current major line, or an
exact pin used to freeze a repository, and restored a check frequency the
consumer had deliberately slowed or disabled. Because the pin is what decides
whether a major upgrade is offered at all, this silently changed update
behaviour on a command the consumer would reasonably expect to be idempotent.

This is the same defect already fixed in update.sh and update.ps1 in an
earlier commit; deploy was missed, so the two disagreed about who owns those
fields. Both now read an existing manifest and preserve both values, falling
back to the derived defaults only when there is no manifest or it cannot be
parsed.

Verified in both implementations: a fresh deployment still gets "1.x" and
"weekly", and a redeploy over a manifest edited to "*" and "monthly" keeps
both.

Also corrects the usage comment in the test suite, which still pointed at the
pre-move ./tests/test-deploy.sh path, and the matching reference in deploy.sh.
The suite must be invoked as bash scripts/tests/test-deploy.sh.

Adds TC16 covering the defaults and both preserved fields. 108 bash
assertions, up from 105.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings September 6, 2026 21:34
@ldastey-dev

Copy link
Copy Markdown
Owner Author

Three further suppressed findings, all accepted and fixed in e4430d3.

deploy.sh:302 and deploy.ps1:756 — redeploy reset consumer manifest configuration

Correct, and this is the same defect I fixed in update.sh and update.ps1 earlier in the review; deploy was missed, so the two disagreed about who owns those fields. Both scripts rewrote pin and checkFrequency unconditionally, so a redeploy reset a pin of * back to the current major line, or an exact pin used to freeze a repository, and restored a check frequency the consumer had deliberately slowed.

That matters more than a stray config value: the pin is what decides whether a major upgrade is offered at all, so a redeploy silently re-armed the exact behaviour a consumer had opted out of — on a command they would reasonably expect to be idempotent.

Both now read an existing manifest and preserve both values, falling back to the derived defaults only when there is no manifest or it cannot be parsed. Verified in both implementations:

deploy.sh  : fresh-defaults=[1.x weekly]  after-redeploy=[* monthly]
deploy.ps1 : fresh-defaults=[1.x weekly]  after-redeploy=[* monthly]

TC16 covers the defaults and both preserved fields, so a fresh deployment still gets sensible values and the fix cannot silently become "never write the manifest".

scripts/tests/test-deploy.sh:6 — stale usage path

Correct; a leftover from moving the suite into scripts/. Corrected here and in the matching reference in deploy.sh. Worth noting the accurate invocation is bash scripts/tests/test-deploy.sh.

108 bash assertions, up from 105. All 12 checks pass.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

It introduces and wires together deploy/update/migration tooling plus release automation workflows, so correctness depends on many interacting components and warrants final human review.

Review details

Suppressed comments (1)

Previously missed (1) — in code that hasn't changed since the last review.

core/AGENTS.md:130

  • The managed-block guidance for session start runs update.sh --check without --quiet, but --check (when up to date) calls report_local_state, which hashes the entire .context tree and can print multiple lines (diverged/orphaned files). That conflicts with the stated goal here (“Report at most one line”) and with the design goal of a cheap session-start check; using --quiet avoids both by exiting before the expensive local-state scan and suppressing offline noise.
  • Files reviewed: 29/34 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

Actions the remaining suppressed finding from the latest automated review.

The managed block told agents to run "update.sh --check" while also requiring
"Report at most one line" and a cheap session-start check. Those conflict:
without --quiet, the up-to-date path prints a line and then calls
report_local_state, which hashes the whole context tree and prints a further
line for every locally edited or orphaned file.

That cost has just grown. Expanding the manifest to track non-markdown base
files took the scan from 78 files to 87, so the guidance was pulling in the
opposite direction to the design goal it sits under.

--quiet is built for exactly this: it exits before the local-state scan, stays
silent when the check is offline or already current, and still prints the
one-line notice when an update is available - which is the only case the agent
needs to surface. Verified the offline path is silent under --quiet and prints
a line without it, and that both implementations gate at the same three points.

The manual invocations in README.md and the overrides README keep plain
--check, where the local-state report is the reason a person runs it. README
now documents --quiet, the trade-off between the two, and which is intended
for whom.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings September 6, 2026 21:49
@ldastey-dev

Copy link
Copy Markdown
Owner Author

Final suppressed finding, accepted and fixed in 59353d3.

core/AGENTS.md:130 — the session-start check was not using --quiet

Correct, and this one matters more than its size suggests, because it works directly against the constraint the whole mechanism was designed around. The managed block asked agents to run update.sh --check while simultaneously requiring "Report at most one line" and a cheap session-start check. Those conflict: without --quiet, the up-to-date path prints a line and calls report_local_state, which hashes the entire context tree and prints a further line per locally edited or orphaned file.

That cost had just grown as a direct result of an earlier fix in this same review: expanding the manifest to track non-markdown base files took the scan from 78 files to 87. So the guidance was pulling against the design goal it sits underneath, and getting worse.

--quiet is built for precisely this case. It exits before the local-state scan, stays silent when the check is offline or already current, and still prints the one-line notice when an update is available — which is the only case an agent needs to surface. Confirmed the gating is identical in both implementations (three matching exit points), and that the offline path is silent under --quiet and prints a line without it.

The manual invocations in README.md and the overrides README deliberately keep plain --check: there, the local-state report is the whole reason a person runs the command. The README now documents --quiet, the trade-off, and which form is meant for whom — it previously did not mention the flag at all.


All review comments on this pull request are now actioned. 34 inline threads replied to and resolved, plus 16 suppressed findings addressed across five rounds. Three were rejected on evidence and documented: find -maxdepth (portable on BSD/macOS), -UseBasicParsing (present and functional on PowerShell 7.6.3), and the pr-title type list (a complementary layer to the patch floor, not a contradiction).

Test coverage grew from 81 to 108 bash assertions and 20 to 32 Pester assertions over the course of the review, with each new assertion confirmed to fail when its defect is reintroduced. All 12 checks pass.

Two items remain for you, neither deliverable through this pull request:

  1. Enable squash-only merge in the repository's merge settings. The release job derives the bump from the merge commit subject, which only equals the pull request title under squash-merge. It currently warns when the subject is not a Conventional Commit; say the word if you would prefer a hard failure.
  2. This branch is not to be merged by me — it is ready for your review and manual testing.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

scripts/update.sh contains a broken sed-escape expression and an unguarded mktemp -d in --apply that can lead to incorrect behaviour and unsafe filesystem writes.

Review details

Suppressed comments (3)

Previously missed (1) — in code that hasn't changed since the last review.

scripts/lib/common.ps1:201

  • The comment says this function hashes only base ".md" files, but the implementation intentionally hashes all file types (no extension filter) to catch edits to non-markdown companions. Update the comment to match the actual behaviour so future changes don’t reintroduce the markdown-only bug.

scripts/update.sh:105

  • The sed expression used to escape regex metacharacters is malformed because it includes an unescaped '/' delimiter inside the pattern, which will cause sed to parse the command incorrectly and break divergence detection. Use a non-'/' delimiter (e.g. '|') so '/' can be safely included in the character class.
    scripts/update.sh:222
  • In --apply mode this script does not run under set -e, so a failing mktemp -d will not stop execution and can leave WORK_DIR empty, causing subsequent operations to write to unexpected paths (e.g. /src.tar.gz) and potentially damage the system. Guard the mktemp call and exit if it fails before proceeding.
  • Files reviewed: 29/34 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The deploy and update flows have confirmed inconsistencies that can make deployments report a version/routing state that doesn’t match the actual on-disk base content (notably under no-overwrite redeploys and when index.md is absent upstream).

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details

Suppressed comments (2)

scripts/deploy.sh:842

  • deploy.sh always rewrites .context/VERSION and .context/manifest.json to DEPLOY_VERSION even when running with --no-overwrite on an existing deployment (where most base files are intentionally skipped). That can make the deployment report a newer version than the files it actually contains, which will confuse update.sh (--status/--check) and any pinning logic.
    scripts/deploy.ps1:773
  • deploy.ps1 writes .context/VERSION and manifest.json using $DeployVersion even under -NoOverwrite on an existing deployment (where Copy-SingleFile may skip most base files). This can leave a deployment reporting a version it does not actually contain, which will mislead update.ps1 and any pin/version comparisons.
  • Files reviewed: 29/34 changed files
  • Comments generated: 2
  • Review effort level: Lite

Comment thread scripts/update.ps1 Outdated
Comment thread scripts/update.sh Outdated
The character class escaped "/", which is neither a regex metacharacter
nor the delimiter of the substitution it feeds, so the backslash it
produced was undefined behaviour under POSIX. It also omitted "|", which
is the actual delimiter, so a path containing one could terminate the
pattern early.

Escape "|" and drop "/". Verified that "a.b.md" still matches its
manifest entry and that "indexXmd" still does not match "index.md".

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings September 6, 2026 22:03

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

The PowerShell shared library hard-codes upstream repo/URL defaults (unlike bash), and the overrides documentation currently promises an update.sh warning that is not implemented.

Review details

Suppressed comments (3)

Previously missed (3) — in code that hasn't changed since the last review.

scripts/lib/common.ps1:11

  • scripts/lib/common.sh allows consumers (or forks) to override the upstream repo/URLs via environment variables (AC_SOURCE_REPO / AC_RAW_BASE / AC_WEB_BASE), but the PowerShell library hard-codes these values. This makes deployments created with deploy.ps1 always write a manifest pointing at 'ldastey-dev/agentic-context', even if the consumer is deploying from a fork, which will cause update.ps1/migrate.ps1 to fetch from the wrong upstream.
    core/.context/overrides/README.md:89
  • This doc claims update.sh warns when an override file’s overrides: frontmatter target doesn’t match the override’s own path, but neither update.sh nor update.ps1 implements that check (they currently only report orphan overrides whose target no longer exists). As written, this promise is misleading to consumers.
    scripts/lib/common.ps1:205
  • The comment above Get-AcContextHashes says it hashes "every base .md file", but the implementation intentionally hashes all base files (no *.md filter) to detect divergence in non-markdown playbook companions. This mismatch is likely to confuse future edits and reviewers.
  • Files reviewed: 29/34 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

The apply path copied index.md only when it was present in the
downloaded archive. A payload missing it left the previous index.md in
place while every other area was replaced wholesale, so a deployment
could carry a stale routing table indefinitely while the manifest and
VERSION advanced.

index.md is the routing table; without it no standard or playbook is
discoverable, so it is mandatory base content. Validate it alongside the
other areas before anything is deleted, and copy it unconditionally.
Deleting it when absent would be worse than keeping it, so refusing the
payload is the correct outcome.

Applied to both implementations.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings September 6, 2026 22:07

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

There are confirmed portability/contract mismatches (GNU-only sort -z in new deploy helpers, and mode flag exclusivity/doc claims in the override README) that should be corrected before merge.

Review details

Suppressed comments (6)

Previously missed (4) — in code that hasn't changed since the last review.

scripts/deploy.sh:181

  • sort -z is a GNU extension and is not available in BSD sort on macOS, which conflicts with this repo’s “macOS bash 3.2 + BSD userland” portability guarantee. This function can keep deterministic ordering without NUL-sorting by sorting newline-delimited paths and reading with read -r (paths in this repo don’t contain newlines).

This issue also appears on line 208 of the same file.
scripts/update.ps1:34

  • Mode selection currently allows multiple switches and has surprising precedence (e.g., -Apply -Check ends up in check because of assignment order). Given the documented usage, treat -Check, -Apply, and -Status as mutually exclusive and fail fast when more than one is set.
    scripts/update.sh:56
  • The CLI usage advertises mutually exclusive modes ([--check|--apply|--status]), but the parser currently allows multiple mode flags and silently lets the last one win. That’s easy to mis-invoke in scripts and hard to diagnose; it should error when more than one mode is provided.
    core/.context/overrides/README.md:3
  • This says the framework “never reads, writes or deletes anything” in .context/overrides/, but the framework does seed files into this directory on deploy, and the updater reads override frontmatter to report orphaned overrides. Consider rephrasing to the actual contract: framework tooling never overwrites or deletes consumer override content.

This issue also appears on line 87 of the same file.

scripts/deploy.sh:212

  • Same portability issue here: sort -z is GNU-specific and will fail under BSD sort on macOS. Prefer newline sorting with LC_ALL=C sort and read -r to keep deterministic behaviour without relying on -z.
    core/.context/overrides/README.md:89
  • The README claims update.sh warns when an override’s overrides: frontmatter doesn’t match the override’s own path, but the current tooling only reports orphaned targets (missing base files). Either implement the mismatch check or adjust this doc to avoid promising behaviour that doesn’t exist.
`overrides` must match the file's own location. `.context/overrides/standards/security.md`
must declare `overrides: standards/security.md`. `update.sh` warns when they disagree,
because a mismatch usually means a file was copied and the frontmatter was not updated.
  • Files reviewed: 29/34 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants