feat: add semantic versioning, CI release automation and the override model - #31
ldastey-dev wants to merge 20 commits into
Conversation
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>
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>
There was a problem hiding this comment.
🟡 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 deployedAGENTS.mdso 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.
There was a problem hiding this comment.
🟡 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.shruns in--applymode and performs destructive operations (rm/cp/tar), but the script is not running withset -e, so a failed command can be silently ignored and leave the deployment partially updated (and then still rewritemanifest.json). Enabling-ereduces the risk of ending up with a corrupted base tree.
scripts/migrate.sh:15migrate.sh --applyperforms filesystem moves/copies that must either complete or fail loudly, but the script doesn't useset -e, so errors fromcp,tar,sed, etc. may be missed and migration could complete with an inconsistent state. Usingset -emakes the migration safer and more predictable.
set -uo pipefail
scripts/lib/common.ps1:235
Write-AcManifestalways setspinto<major(version)>.x, which means any consumer who edited the manifest to pin to*or an exact version will have their choice overwritten on everyupdate.ps1 -Apply/migrate.ps1 -Apply. Consider adding an explicit-Pinparameter (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.*.jsongoing 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
--applyrewritesmanifest.jsonand currently hard-resetspinto<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
…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>
There was a problem hiding this comment.
🔵 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 -Applyreads the current manifest pin into$Pin, but then rewrites manifest.json viaWrite-AcManifest, which always setspinto<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 extendingWrite-AcManifestto accept-Pinand passing$Pinhere).
scripts/update.sh:304
update.sh --applyrewrites 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 existingPINfrom the current manifest when present, and only default to<major>.xwhen 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--overwriteis 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-Overwriteis 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>
There was a problem hiding this comment.
🔵 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
mktempwithout a template fails on macOS/BSDmktemp, so managed-block replacement can break on a supported platform. Use a BSD-compatiblemktemp -tfallback for temp files.
scripts/migrate.sh:120mktempwithout a template fails on macOS/BSDmktemp, so the migration script will fail on a supported platform. Use a BSD-compatiblemktemp -tfallback for temp files.
This issue also appears on line 243 of the same file.
scripts/update.ps1:266
Write-AcManifestalways recomputespinfrom 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:214mktemp -dwithout a template fails on macOS/BSDmktemp, soupdate.sh --applywill 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.shwarns when an override’soverrides: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
--applyrewrites manifest.json withpinforced to<major>.x, which overwrites any consumer-configured pin (e.g.*or an exact version) and breaks the advertised pinning behaviour. Preserve the existingpinvalue when present, only defaulting when it was missing.
scripts/migrate.sh:243mktempwithout a template fails on macOS/BSDmktemp, so this temporary file creation is not portable as written.
tmp="$(mktemp)"
scripts/lib/common.ps1:232
Write-AcManifesthard-codespinto<major>.x, which means update flows overwrite any consumer-configured pin in an existing manifest. To support pinning as an owned consumer setting,Write-AcManifestshould 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.*.jsonpatterns).
{
"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>
0a4b7bc to
f41aa5a
Compare
There was a problem hiding this comment.
🟡 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
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>
There was a problem hiding this comment.
🟡 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
There was a problem hiding this comment.
🟡 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 failedcphere can leave.context/bin/*partially updated while the script continues, rewritesmanifest.jsonandVERSION, and reports success. This also contradicts the comment above stating every operation is checked explicitly.
scripts/lib/common.sh:195 ac_hash_context_treeis described as hashing “every deployable file”, but it currently hashes only*.md. Sincescripts/deploy.shcopies all files underplaybooks/(including non-markdown companions likeplaybooks/setup/create-local-otel-stack/validate-config.sh,docker-compose.yaml,versions.env, etc.), those base files are not tracked inmanifest.jsonand 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
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>
|
Two further suppressed findings, both accepted and fixed in
Correct, and it contradicted my own comment directly above the block claiming every operation there is checked. I had routed the area copies through
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.
Hashing now covers all base content in both languages, excluding And the behaviour that was missing now works: One part deliberately not widened.
|
There was a problem hiding this comment.
🔵 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 underscripts/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
There was a problem hiding this comment.
🔵 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>
|
Three further suppressed findings, all accepted and fixed in
Correct, and this is the same defect I fixed in 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:
Correct; a leftover from moving the suite into 108 bash assertions, up from 105. All 12 checks pass. |
There was a problem hiding this comment.
🔵 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 --checkwithout--quiet, but--check(when up to date) callsreport_local_state, which hashes the entire.contexttree 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--quietavoids 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>
|
Final suppressed finding, accepted and fixed in
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 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.
The manual invocations in 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: 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:
|
There was a problem hiding this comment.
🔵 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 failingmktemp -dwill 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
There was a problem hiding this comment.
🟡 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
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>
There was a problem hiding this comment.
🔵 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.shwarns when an override file’soverrides: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>
There was a problem hiding this comment.
🔵 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 -zis a GNU extension and is not available in BSDsorton 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 withread -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 -Checkends up incheckbecause of assignment order). Given the documented usage, treat-Check,-Apply, and-Statusas 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 -zis GNU-specific and will fail under BSDsorton macOS. Prefer newline sorting withLC_ALL=C sortandread -rto keep deterministic behaviour without relying on-z.
core/.context/overrides/README.md:89 - The README claims
update.shwarns when an override’soverrides: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
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.
.context/{standards,playbooks,conventions,index.md}.context/overrides/AGENTS.mdBecause 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) ormode: replace(yours wins outright).Cost
The staleness check is a single fetch of a six-byte
VERSIONfile 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
VERSIONat 1.0.0, plusscripts/baselines/1.0.0.sha256hashingmain'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.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 acrossREADME.md,MIGRATIONS.mdand the maintainerAGENTS.md.Design decisions worth reviewing
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.VERSION, so a consumer resolving that tag downloads content contradicting the version it was told to expect.MIGRATIONS.mdmeaningful.contents: write; the changelog and baseline generators are a few dozen lines of portable shell instead.Testing
shellcheck --severity=warningmigrate.shandmigrate.ps1were 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
VERSIONand the tag. If protection is reinstated it must be a ruleset withgithub-actions[bot]inbypass_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 --applyagainst a real newer tag.Please do not merge without reviewing. Merging this cuts
1.0.0and tagsv1.0.0.