Skip to content

fix(windows): vswhere detection + winget build-tools install; reconcile fork-origin onto current main - #3

Merged
noogalabs merged 1 commit into
mainfrom
fix/windows-install-reconcile-mjs
Jun 14, 2026
Merged

noogalabs merged 1 commit into
mainfrom
fix/windows-install-reconcile-mjs

Conversation

@noogalabs

Copy link
Copy Markdown
Owner

What & why

Reconciles the field-proven Windows installer fix (PR #1 fix/windows-install-mjs, which got Foothills PM running on Windows) onto current main, and adds the Windows guide (PR #2 docs/windows-install-guide). PR #1 didn't cherry-pick cleanly because main already had a simpler post-rename origin check while PR #1 carried richer logic against the old pre-rename name. Resolved by hand.

Blast radius note: install.mjs is the live public install script every new member runs. Mac/Linux zero-regression is the gate, and is verified byte-level below.

1. Windows build-tools block — before/after

The dead npm install -g windows-build-tools package (long deprecated/removed) is GONE. Replaced with vswhere DETECTION + winget install.

Before (main):

// Check for Windows Build Tools
let hasBuildTools = false;
try { run('cl.exe /? 2>&1'); hasBuildTools = true; } catch {}
try { run('where cl.exe'); hasBuildTools = true; } catch {}
...
console.log(`    ${Y}  npm install -g windows-build-tools${R}`);
...
if (tryAuto) { runVisible('npm install -g windows-build-tools'); ... }

After (this branch):

// cl.exe is only on PATH inside a Developer PowerShell — fall back to vswhere.
let hasBuildTools = false;
try { run('where cl.exe'); hasBuildTools = true; } catch {}
if (!hasBuildTools) {
  const vswhere = join(process.env['ProgramFiles(x86)'] || 'C:\\Program Files (x86)',
    'Microsoft Visual Studio', 'Installer', 'vswhere.exe');
  if (existsSync(vswhere)) {
    try {
      const vcPath = run(`${JSON.stringify(vswhere)} -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath`);
      if (vcPath) hasBuildTools = true;
    } catch {}
  }
}
...
console.log(`    ${Y}  winget install Microsoft.VisualStudio.2022.BuildTools --override "--passive --wait --add Microsoft.VisualStudio.Workload.VCTools --includeRecommended"${R}`);
...
if (tryAuto) { runVisible('winget install Microsoft.VisualStudio.2022.BuildTools --override "..."'); }

The final C++-compilation-error hint (was L613) was also switched from the dead npm package to the winget command. AUTO_BUILD_TOOLS=1 env gate is retained — it now triggers the winget auto-install, not the dead package.

2. Resolved fork-origin block

Kept PR #1's richer isCanonicalOrigin / isForkOrigin logic (handles a personal fork as origin → adds canonical upstream instead of silently skipping migration), but corrected the repo-name references to the post-rename noogalabs/ascendops (PR #1 referenced the old noogalabs/ascendops-install). Main's existing canonical-match behavior (includes('noogalabs/ascendops'), grandamenium/cortextos, === REPO_URL) is preserved verbatim — the new branch only adds the fork case on top.

const repoName = REPO_URL.replace(/^https:\/\/github\.com\//, '').replace(/\.git$/, '').split('/').pop();
const isCanonicalOrigin = !!originUrl && (
  originUrl.includes('noogalabs/ascendops') ||
  originUrl.includes('grandamenium/cortextos') ||
  originUrl === REPO_URL
);
const isForkOrigin = !!originUrl && !isCanonicalOrigin && (
  originUrl.endsWith(`/${repoName}.git`) || originUrl.endsWith(`/${repoName}`)
);
if (isCanonicalOrigin) { /* rename origin -> upstream (unchanged from main) */ }
else if (isForkOrigin) { /* add canonical as upstream, keep fork as origin */ }

3. Mac/Linux paths untouched — verification

The full diff to install.mjs is exactly these hunks (git diff main):

@@ -261,39 +261,57 @@ if (IS_MAC) {     # Windows build-tools block only
@@ -480,10 +498,23 @@ ...                # fork-origin block
@@ -492,6 +523,17 @@ ...                 # fork-origin block (cont.)
@@ -609,8 +651,8 @@ try {              # Windows-only final-error hint

All four hunks are inside the IS_WINDOWS branch or the fork-origin migration block. A line-by-line script that strips those three regions from both main and this branch confirms the remainder is byte-identical — the only other delta is the deliberate removal of one now-inaccurate comment line in the fork block. The IS_MAC (brew/xcode-select) and IS_LINUX (apt-get/build-essential) paths are unchanged.

No ascendops-install regression in install.mjs (main's canonical name preserved). Guide install URLs updated to the canonical name.

4. Gates

  • node --check install.mjsCLEAN
  • No secrets/keys in the diff (scanned).

Do not merge — leaving for human review.

…concile fork-origin onto post-rename repo name

Reconciles the field-proven Windows installer fix (PR #1, fix/windows-install-mjs,
which got Foothills PM running) onto current main, and adds the Windows guide
(PR #2, docs/windows-install-guide).

install.mjs:
- Replace the dead `npm install -g windows-build-tools` path (package deprecated/
  removed) with vswhere DETECTION + `winget install Microsoft.VisualStudio.2022.
  BuildTools` install. vswhere finds the toolset even when cl.exe isn't on PATH
  (the false "build tools missing" failure mid-install).
- Drop the redundant `cl.exe /?` probe; keep `where cl.exe` + vswhere fallback.
- Update the final C++-error hint (was L613) to the winget command.
- Adopt PR #1's richer isCanonicalOrigin / isForkOrigin fork-migration logic,
  reconciled to the POST-RENAME canonical name noogalabs/ascendops (PR #1 was
  authored pre-rename and referenced noogalabs/ascendops-install).

Guides (WINDOWS-INSTALL.md, PRE-CALL-CHECKLIST.md): install URLs updated to the
canonical noogalabs/ascendops name.

Mac and Linux code paths are byte-untouched (verified: outside the Windows
build-tools hunk and the fork-origin hunk, the file is identical to main).
node --check passes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@noogalabs
noogalabs merged commit 68ee431 into main Jun 14, 2026
6 checks passed
@noogalabs
noogalabs deleted the fix/windows-install-reconcile-mjs branch June 14, 2026 21:46

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 24a4ac72a8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread WINDOWS-INSTALL.md
Comment on lines +10 to +12
> **Why a separate Windows guide?** The Mac install (SKOOL-INSTALL.md) is a couple
> of commands. Windows needs three extra things set up by hand — Node.js, Git, and
> the Visual C++ build tools — and the order matters. This guide walks each one.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Add WSL to the Windows setup path

For a zero-start Windows machine without WSL, this guide walks through only Node, Git, and Visual C++ Build Tools before running the installer, but the installer itself checks wsl and warns that “agents will not work without it on Windows” because shell scripts run in bash. Following this guide as written can therefore complete the install flow and proceed to onboarding with a fleet that cannot run its Windows shell-script dependency; add a WSL install/restart step before Step 6 or list it as a required prerequisite here.

Useful? React with 👍 / 👎.

noogalabs pushed a commit that referenced this pull request Jun 30, 2026
…o-write (Codex P2 #3, docs side)

Third trigger of the same onboarded-without-crons class: the daemon retro-write
(agent-process.ts:1006-1029) marks an agent onboarded if MEMORY.md is >80 chars
AND has no <!--, OR a heartbeat.json exists. The interview fills IDENTITY ## Name
early, so MEMORY.md's <!-- comment is the only remaining content-guard; if it is
dropped before .onboarded is written, a crash/restart lets the daemon retro-write
and skip cron registration.

Docs-side fix (this commit): strip the MEMORY.md template comment ONLY inside the
final &&-chain, AFTER the role crons register and immediately BEFORE touch
.onboarded (portable grep -vF into a same-dir tmp + mv, && chained so a strip
failure halts before touch). The interview keeps the comment until then. Added an
INVARIANT guard comment in all 3 ONBOARDING.md: no heartbeat write before the
final block (it would reopen the existsSync(heartbeatPath) branch). Committed
MEMORY.md retains <!-- so the bundle ships un-onboarded by content.

Tests: +3 assertions (committed MEMORY.md has <!--; strip sits between crons and
touch; negative-control guard that strip is not before the crons). 20/20 green,
bash -n clean x3, halt-proof both branches (leftover -> comment RETAINED + no
.onboarded; clean -> comment GONE after 6 crons), negative-control verified red.

NOTE: the heartbeat OR-branch (fast-checker.ts 50-min watchdog) is daemon-side and
handled in a SEPARATE cortextos fork PR (fire-time .onboarded gate). This bundle PR
HOLDS to merge together with that daemon PR; the content branch alone is not
sufficient.
noogalabs added a commit that referenced this pull request Jun 30, 2026
…or classroom bundles (#18)

* feat(community): guided onboarding v2 - reverse-prompting interview for classroom bundles

The classroom download bundles previously required hand-editing ~13 bootstrap
files. v2 makes a fresh agent INTERVIEW the operator over Telegram on first boot
and configure itself from the answers (reverse prompting: the agent asks, the
operator answers, the agent writes its own config).

Trigger (the framework's own mechanism, restored):
- IDENTITY '## Name' ships an HTML-comment marker ('<!-- Set during onboarding -->')
  instead of {{agent_name}}, plus a '<!--' marker in MEMORY. hasCompletedBootstrapContent
  (agent-process.ts) treats '<!--' as "still a template", so it does NOT retro-write
  .onboarded on first boot -> isOnboarded=false -> buildStartupPrompt injects the
  first-boot prompt -> the onboarding skill runs the interview. The interview writes
  .onboarded at the end, so it never re-fires. This is exactly how the hub's own
  templates/agent-leasing-coordinator onboards; the classroom bundles had lacked it
  ({{agent_name}} in ## Name read as "complete" -> onboarding was skipped).

Adds per bundle:
- ONBOARDING.md: the role-specific reverse-prompting interview (accounting / leasing /
  maintenance). Asks for company/owner/timezone, software stack, and the operator's
  SOPs/preferences; writes IDENTITY/USER/SYSTEM/goals + ingests SOPs to the KB; adds
  the recommended role crons as the final step; writes .onboarded. Copilot-first.
- .claude/skills/onboarding/SKILL.md: the generic onboarding skill (scrubbed clean).

Ship-time config stays enabled=true + crons=[] (the interview adds crons post-config),
so the cron-safety test still holds. The onboarding files pass verify-clean-gate
(scrub gate-enforced: planting our-data in them goes DIRTY).

Test: classroom-agents-cron-safe.test.ts now also guards the trigger contract
(IDENTITY marker + ONBOARDING.md + onboarding skill present per bundle) and exempts
ONBOARDING.md / the onboarding skill from the no-cron-in-docs check (adding crons
post-interview is intended). 13/13 pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018XzqKvXygRBULsAxEHMyse

* fix(community): onboarding sets ## Name to the add-agent slug (Codex P2)

cortextos add-agent's copyTemplateFiles rewrites {{agent_name}} -> the CLI agent
slug BEFORE ONBOARDING.md is ever read (add-agent.ts:421). So by interview time
there are no {{agent_name}} placeholders left, only the ## Name marker. Asking
the operator for a name in the interview therefore only changed ## Name while
SOUL/SYSTEM/cron-prompts kept the slug = inconsistent.

Fix: the agent's name IS the slug the operator chose at `add-agent <name>` (already
filled consistently everywhere). Onboarding now sets ## Name to $CTX_AGENT_NAME,
drops the no-op "replace {{agent_name}} everywhere" instruction, and the add-cron /
list-crons commands use "$CTX_AGENT_NAME". The page docs note: pick the name you
want at add-agent time. (A different display name remains optional, with a note to
keep $CTX_AGENT_NAME for commands/bus.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018XzqKvXygRBULsAxEHMyse

* fix(community): normalize remaining list-crons agent arg to $CTX_AGENT_NAME (Aussie)

Aussie's completeness pass caught a residual of the same name-consistency class: a
leasing ONBOARDING.md list-crons command still used the <your-agent-name>
placeholder. Swept ALL three ONBOARDING.md for any placeholder-agent-arg
(<your-agent-name>/<this-agent-name>/<agent-name>) in agent-run commands and
normalized them all to "$CTX_AGENT_NAME". The README keeps <your-agent-name> on
purpose: that is the member-run MANUAL path where it is a fill-in placeholder, not
an agent-run command.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018XzqKvXygRBULsAxEHMyse

* fix(community): comprehensive placeholder hard-gate + Telegram-cred restart (Codex 3xP2)

Codex caught 3 real P2s on the guided-onboarding v2 (add-agent only fills
{{agent_name}}/{{org}}, so every other {{...}} is the interview's job):

1+2. Placeholder coverage gap: {{company_name}} lives in CLAUDE.md, sibling-agent
   names ({{leasing_agent_name}}/{{maintenance_agent_name}}) and rubric vars live in
   .claude/skills/**/SKILL.md - none covered by the old hardcoded bootstrap-file
   replacement lists. A member could write .onboarded with literal placeholders in
   Claude's main prompt or a scheduled skill. Fix: a HARD GATE before .onboarded in
   both the shared onboarding skill (Step 4) and each ONBOARDING.md:
     if grep -rlE '\{\{[^{}]+\}\}' . --include='*.md' --include='*.json' \
        | grep -vE 'ONBOARDING.md|README.md|skills/onboarding/|node_modules'; then STOP
     else touch .onboarded; fi
   Recursive (reaches CLAUDE.md + all skills), format-COMPLETE ({{[^{}]+}} catches any
   placeholder format incl digits/hyphens/uppercase, not just lowercase_underscore),
   and excludes the self-referencing setup docs so it PASSES once runtime files are
   filled (verified halts-on-leftover AND passes-when-clean).

3. Telegram-cred restart: a fresh agent that writes BOT_TOKEN/CHAT_ID via
   detect-chat-id mid-interview wont receive inbound replies until restart (the poller
   is built from .env at spawn). Step 0 now restarts after writing creds; primary path
   stays "set .env before first start" per the page docs.

CI guard: classroom-agents-cron-safe.test.ts asserts each ONBOARDING.md + skill carry
the hard gate (recursive, format-complete, excludes setup docs) and a behavioral test
that the gate regex catches a future-format placeholder and passes clean text. 17/17.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018XzqKvXygRBULsAxEHMyse

* fix(community): gate-before-crons + name-marker in the onboarding gate (Codex 2xP2)

Two ordering/signal edges on the placeholder hard gate:

A. The add-cron commands ran BEFORE the gate, so a missed placeholder let the crons
   persist to daemon state (and fire against a still-templated agent) even though the
   gate then refused .onboarded. Fix: the gate now WRAPS the crons - they live in the
   else branch, so crons are persisted only when the gate passes clean. Gate-then-crons.

B. The gate grepped only {{...}}, not the ## Name '<!-- Set during onboarding -->'
   marker. Fill-every-placeholder-but-leave-the-marker passed the gate and wrote
   .onboarded, leaving the agent booting "onboarded" with a template marker as its
   display name (the daemon's hasCompletedBootstrapContent keys on '<!--' in ## Name).
   Fix: the gate also rejects the marker - it now covers every signal the daemon
   trigger keys on ({{...}} OR the name marker), in both the ONBOARDING.md and the
   shared onboarding skill.

Halt-proofed both directions: ship-time -> gate halts (0 crons); all {{...}} filled but
marker left -> halts on the marker; all filled + marker replaced -> passes (crons +
.onboarded). CI test extended: gate covers {{...}}|marker, crons are inside the else
(no add-cron before the gate), and the behavioral check halts on a leftover, a future
format, and the marker, and passes clean. 17/17.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018XzqKvXygRBULsAxEHMyse

* fix(community): restore eaten kb-ingest + drop stray template line + Step 0 restart (Codex 2xP2)

Two paste-path edges on b533836:

1. The shared onboarding skill Step 0 had the restart-after-detect-chat-id caveat
   but each ONBOARDING.md's own Step 0 (which the first-boot prompt sends the agent
   to) did not. A member who did not prefill .env wires the bot there, then cannot
   receive replies until restart. Added the restart caveat to the accounting and
   maintenance ONBOARDING.md Step 0 (leasing Step 0 does not write creds).

2. Maintenance: the gate-wrap restructure matched the PROSE template line
   (`add-cron "$CTX_AGENT_NAME" <name> "<schedule>"...`) as if a real cron, which
   region-shifted and (a) left that literal-<name> line inside the gate else (would
   break the pasted block) and (b) ate the make-ready-sops kb-ingest command. Removed
   the stray line and restored the kb-ingest. accounting/leasing were unaffected
   (their first add-cron is a real cron, not prose).

Verified the WHOLE maintenance file (not just the 3 spots): paste-path `bash -n`
clean (no literal <name>/<schedule>), code fences balanced, Steps 0-7 coherent, all
3 kb-ingests present, 5 crons in the else matching the README. 17/17.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018XzqKvXygRBULsAxEHMyse

* fix(community): leasing Step 0 Telegram wiring + atomic-on-success cron completion (Codex P2+P3)

1. P2 - leasing parity: leasing ONBOARDING.md had no Telegram-credential check (cred-
   check presence was accounting=1, maintenance=3, leasing=0), yet its reverse-prompting
   interview is over Telegram and starts by SENDING a message. A fresh leasing agent with
   an empty .env would stall. Added the same Step 0 bot-wiring + restart caveat as the
   other two (before the intro).

2. P3 - atomic-on-success: the gate else-block ran the add-cron commands then `touch
   .onboarded` SEQUENTIALLY with no `set -e`/`&&`, so a failed add-cron (non-zero on a bad
   arg) fell through and marked onboarding complete with a missing cron (silent failure,
   no retry). All 3 bundles now &&-chain crons -> list-crons -> mkdir -> touch .onboarded
   -> echo, with an `|| echo "STOP: a cron failed ... .onboarded was NOT written ..."`
   fallback. The marker is written only on a fully-successful setup.

Halt-proofed: bash -n clean on all 3 gate blocks; a simulated add-cron failure aborts the
&& chain BEFORE touch, so .onboarded is NOT written and the operator is told to fix+re-run.
17/17.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018XzqKvXygRBULsAxEHMyse

* fix(community): idempotent cron cleanup so a partial-fail re-run is not stuck (Aussie)

The &&-chain (atomic-on-success) correctly refuses .onboarded if an add-cron fails,
but cortextos bus add-cron throws on a duplicate cron name (crons.ts:229, not
idempotent). So a PARTIAL failure (crons 1-2 persist, 3 fails) left .onboarded
unwritten AND crons 1-2 persisted - re-running the block would re-add the existing
crons 1-2, error on the duplicate, and never reach .onboarded = permanently stuck.

Fix: each gate else-block first runs an idempotent remove-cron cleanup over all the
cron names (remove-cron never throws, returns false if absent), THEN the &&-chained
add-cron sequence. So a re-run after a partial failure clears the prior crons and
re-adds them cleanly; a real add-cron failure (bad arg) still aborts the chain before
.onboarded.

Halt-proofed: bash -n clean; partial-prior-run (a cron pre-persisted) -> cleanup
clears it -> add-chain completes -> reaches .onboarded (rerun-safe). 17/17.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018XzqKvXygRBULsAxEHMyse

* fix(community): single .onboarded completion authority - skill defers to ONBOARDING.md post-cron block (Codex P2)

Dual-completion-path bug: the onboarding SKILL Step 4 ran its own placeholder
sweep and touched .onboarded WITHOUT the role crons, while ONBOARDING.md's
final block touched .onboarded AFTER adding them. An agent following the skill
path reached ONBOARDED-WITHOUT-CRONS, and .onboarded suppresses re-onboarding so
the crons never got added.

Fix: one .onboarded writer. SKILL Step 4 no longer writes .onboarded; it defers
to ONBOARDING.md's final block, which gates on placeholders+marker, adds the role
crons, and only then writes .onboarded (&&-chained, so a cron failure blocks it).
Applied across all 3 bundles (accounting/leasing/maintenance).

Test: the cron-safe contract previously asserted BOTH ONBOARDING.md AND the skill
carry the touch-.onboarded gate (it encoded the dual path). Reworked to enforce
single-authority: ONBOARDING.md carries the full gate+crons+touch; the skill must
NOT touch .onboarded and must point at ONBOARDING.md. 17/17 green.

* fix(community): close MEMORY.md content-branch of the .onboarded retro-write (Codex P2 #3, docs side)

Third trigger of the same onboarded-without-crons class: the daemon retro-write
(agent-process.ts:1006-1029) marks an agent onboarded if MEMORY.md is >80 chars
AND has no <!--, OR a heartbeat.json exists. The interview fills IDENTITY ## Name
early, so MEMORY.md's <!-- comment is the only remaining content-guard; if it is
dropped before .onboarded is written, a crash/restart lets the daemon retro-write
and skip cron registration.

Docs-side fix (this commit): strip the MEMORY.md template comment ONLY inside the
final &&-chain, AFTER the role crons register and immediately BEFORE touch
.onboarded (portable grep -vF into a same-dir tmp + mv, && chained so a strip
failure halts before touch). The interview keeps the comment until then. Added an
INVARIANT guard comment in all 3 ONBOARDING.md: no heartbeat write before the
final block (it would reopen the existsSync(heartbeatPath) branch). Committed
MEMORY.md retains <!-- so the bundle ships un-onboarded by content.

Tests: +3 assertions (committed MEMORY.md has <!--; strip sits between crons and
touch; negative-control guard that strip is not before the crons). 20/20 green,
bash -n clean x3, halt-proof both branches (leftover -> comment RETAINED + no
.onboarded; clean -> comment GONE after 6 crons), negative-control verified red.

NOTE: the heartbeat OR-branch (fast-checker.ts 50-min watchdog) is daemon-side and
handled in a SEPARATE cortextos fork PR (fire-time .onboarded gate). This bundle PR
HOLDS to merge together with that daemon PR; the content branch alone is not
sufficient.

---------

Co-authored-by: David Hunter <davidhunter@Davids-Mac-mini.local>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
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.

1 participant