Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion src/commands/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,13 +95,24 @@ export async function runCommand(

// MODE 0: Org cycle — run all squads as a coordinated system
if (target === '--org' || options.org) {
const { scanOrg, planOrgCycle, displayOrgScan, displayPlan } = await import('../lib/org-cycle.js');
const { scanOrg, planOrgCycle, displayOrgScan, displayPlan, refreshFounderContext } = await import('../lib/org-cycle.js');

writeLine();
const focusLabel = options.focus ? ` ${bold}[${options.focus}]${RESET}` : '';
writeLine(` ${gradient('squads')} ${colors.dim}org cycle${RESET}${focusLabel}`);
writeLine();

// Step 0: REFRESH founder context — distill recent sessions + git activity
// into per-squad alignment files so agents run aligned with the founder's
// current pipeline, not generic squad goals.
if (!options.dryRun) {
const ctxResult = refreshFounderContext();
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The refreshFounderContext call should pass the force option from the CLI to ensure that the --force flag correctly triggers a context refresh even if the files are not yet stale.

Suggested change
const ctxResult = refreshFounderContext();
const ctxResult = refreshFounderContext({ force: options.force });

if (ctxResult === 'failed') {
writeLine(` ${colors.red}Aborting org cycle. Fix the digest script and retry.${RESET}\n`);
return;
}
}
Comment on lines +105 to +114
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The founder context refresh is currently only wired into the --org cycle. However, the PR motivation mentions that single squad runs (e.g., squads run intelligence) also benefit from this alignment. If a user runs a single squad and the context is missing or stale, they won't get the 'live' strategic state. Consider moving this refresh step so it applies to single squad runs as well, perhaps with a shorter timeout or only if the files are missing.


// Step 1: SCAN
const scan = scanOrg();
displayOrgScan(scan);
Expand Down
80 changes: 78 additions & 2 deletions src/lib/org-cycle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@
*/

import { existsSync, readFileSync, readdirSync, statSync } from 'fs';
import { join } from 'path';
import { findSquadsDir, loadSquad } from './squad-parser.js';
import { spawnSync } from 'child_process';
import { join, dirname } from 'path';

Check warning on line 16 in src/lib/org-cycle.ts

View workflow job for this annotation

GitHub Actions / build (22)

'dirname' is defined but never used. Allowed unused vars must match /^_/u

Check warning on line 16 in src/lib/org-cycle.ts

View workflow job for this annotation

GitHub Actions / build (20)

'dirname' is defined but never used. Allowed unused vars must match /^_/u
import { findSquadsDir, loadSquad, findProjectRoot } from './squad-parser.js';
import { findMemoryDir } from './memory.js';
import { colors, bold, RESET, writeLine } from './terminal.js';

Expand Down Expand Up @@ -158,6 +159,81 @@
}
}

/**
* Refresh founder context before an org cycle.
*
* Looks for the digest script at one of two paths (in order):
* - .claude/hooks/founder-context-digest.py (preferred — version-controlled hook)
* - scripts/founder-context-digest.py (fallback — for projects with a scripts/ dir)
*
* Runs the script when `.agents/memory/company/founder-context.md` is missing
* or older than `staleHours` (default 2h). On success, the digest writes:
* - .agents/memory/company/founder-context.md (universal)
* - .agents/memory/{squad}/founder-alignment.md (per-squad)
* which `gatherSquadContext` then injects into every agent's prompt.
*
* Returns:
* 'refreshed' — digest ran successfully and produced fresh files
* 'fresh' — existing context is recent enough, no refresh needed
* 'skipped' — no digest script found at expected paths; nothing to do
* 'failed' — digest exited non-zero; org cycle should NOT proceed
*/
export function refreshFounderContext(
options: { staleHours?: number; force?: boolean } = {}
): 'refreshed' | 'fresh' | 'skipped' | 'failed' {
const projectRoot = findProjectRoot();
if (!projectRoot) return 'skipped';

const candidatePaths = [
join(projectRoot, '.claude', 'hooks', 'founder-context-digest.py'),
join(projectRoot, 'scripts', 'founder-context-digest.py'),
];
const digestScript = candidatePaths.find(p => existsSync(p));
if (!digestScript) return 'skipped';

const memoryDir = findMemoryDir();
const contextFile = memoryDir
? join(memoryDir, 'company', 'founder-context.md')
: null;

const staleHours = options.staleHours ?? 2;
const MS_PER_HOUR = 60 * 60 * 1000;

let isStale = true;
if (!options.force && contextFile && existsSync(contextFile)) {
try {
const ageHours = (Date.now() - statSync(contextFile).mtimeMs) / MS_PER_HOUR;
if (ageHours < staleHours) {
isStale = false;
writeLine(
` ${colors.dim}founder-context: fresh (${ageHours.toFixed(1)}h old, threshold ${staleHours}h)${RESET}`
);
}
} catch { /* */ }
}

if (!isStale) return 'fresh';

writeLine(` ${colors.dim}founder-context: refreshing from CC sessions + git activity...${RESET}`);
Comment on lines +213 to +217
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Hardcoding python3 may cause issues on systems where the executable is named python (e.g., Windows). Additionally, it's safer to check result.error to handle cases where the command fails to start or times out, rather than just checking result.status.

Suggested change
}
if (!isStale) return 'fresh';
writeLine(` ${colors.dim}founder-context: refreshing from CC sessions + git activity...${RESET}`);
const pythonCmd = process.platform === "win32" ? "python" : "python3";
const result = spawnSync(pythonCmd, [digestScript], {
cwd: projectRoot,
stdio: "inherit",
timeout: 12 * 60 * 1000,
});
if (result.error) {
const isTimeout = (result.error as any).code === "ETIMEDOUT";
writeLine(
" " + colors.yellow + "founder-context: digest " + (isTimeout ? "timed out" : "failed to start") + ": " + result.error.message + RESET + "\n"
);
return "failed";
}

// Two Claude calls (universal + per-squad block for all squads in one shot)
// can take 5-8 min on large inputs. Cap at 12 min to be safe.
const result = spawnSync('python3', [digestScript], {
cwd: projectRoot,
stdio: 'inherit',
timeout: 12 * 60 * 1000,
});

if (result.status === 0) {
writeLine(` ${colors.green}founder-context: refreshed${RESET}\n`);
return 'refreshed';
}
writeLine(
` ${colors.yellow}founder-context: digest failed (exit ${result.status ?? '?'}). ` +
`Org cycle blocked — agents would run without strategic alignment.${RESET}\n`
);
return 'failed';
}

/**
* Display execution plan.
*/
Expand Down
125 changes: 90 additions & 35 deletions src/lib/run-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,24 @@
* Squad Context System — context assembly for agent execution.
*
* Layers flow from general to particular (no overrides, each answers a different question):
* L0: SYSTEM.md — How (system, tools, principles — immutable, outside budget)
* L1: company.md — Why (company identity, alignment)
* L2: priorities.md — Where (current focus, urgency)
* L3: goals.md — What (measurable targets)
* L4: agent.md — You (agent role, specific instructions)
* L5: state.md — Memory (continuity from last run)
* L6+: Supporting — feedback, daily-briefing, cross-squad learnings
* L0: SYSTEM.md — How (system, tools, principles — immutable, outside budget)
* L1: company.md — Why (company identity, alignment)
* L2: priorities.md — Where (current focus, urgency)
* L3: goals.md — What (measurable targets)
* L4: agent.md — You (agent role, specific instructions)
* L5: state.md — Memory (continuity from last run)
* L6+: Supporting — feedback, daily-briefing, cross-squad learnings
* L9: founder-context.md — Live strategic state (universal, all squads see)
* L10: founder-alignment.md — Per-squad contribution to founder's current pipeline
*
* L9 + L10 are auto-generated (e.g. by hq/.claude/hooks/founder-context-digest.py) from
* interactive sessions, git activity, and open PRs/issues. They translate the
* founder's live strategic context into per-squad, named contributions so each
* squad shows up aligned with current priorities rather than inventing generic work.
*
* Business-specific structural reference (Drive folder map, ERP architecture,
* canonical sheet schemas) can be embedded inline into founder-context.md by
* the digest script. The CLI loader stays generic; users decide what to embed.
*
* SQUAD.md is metadata only (repo, agents, config) — NOT injected into prompt.
* Each layer adds a unique dimension. No layer contradicts another.
Expand All @@ -31,24 +42,29 @@ export type ContextRole = 'scanner' | 'worker' | 'lead' | 'coo' | 'verifier';
// ── Token Budgets (chars, ~4 chars/token) ────────────────────────────

const ROLE_BUDGETS: Record<ContextRole, number> = {
scanner: 4000, // ~1000 tokens — company + priorities + goals + agent + state
worker: 12000, // ~3000 tokens — + feedback
lead: 24000, // ~6000 tokens — all layers
coo: 32000, // ~8000 tokens — all layers + expanded
verifier: 12000, // similar needs to worker
scanner: 50000, // ~12500 tokens — full founder ctx (incl. embedded Drive structure) + identity layers
worker: 60000, // ~15000 tokens — + feedback + alignment
lead: 80000, // ~20000 tokens — all layers + founder ctx + alignment
coo: 100000, // ~25000 tokens — all layers + expanded + founder ctx
verifier: 60000, // similar needs to worker
};

/**
* Which layers each role gets access to.
* Numbers correspond to layer order in the Squad Context System:
* 1=company, 2=priorities, 3=goals, 4=agent, 5=state, 6=feedback, 7=daily-briefing, 8=cross-squad
* 1=company, 2=priorities, 3=goals, 4=agent, 5=state, 6=feedback,
* 7=daily-briefing, 8=cross-squad, 9=founder-context, 10=founder-alignment
*
* Layers 9 and 10 are visible to ALL roles (including scanners): live strategic
* context is always relevant, regardless of role. Without it, agents invent
* generic work disconnected from the founder's current pipeline.
*/
const ROLE_SECTIONS: Record<ContextRole, Set<number>> = {
scanner: new Set([1, 2, 3, 4, 5]), // identity + focus + role + memory
worker: new Set([1, 2, 3, 4, 5, 6]), // + feedback
lead: new Set([1, 2, 3, 4, 5, 6, 7, 8]), // + daily briefing + cross-squad
coo: new Set([1, 2, 3, 4, 5, 6, 7, 8]), // all layers + expanded budget
verifier: new Set([1, 2, 3, 4, 5, 6]), // same as worker
scanner: new Set([1, 2, 3, 4, 5, 9, 10]), // identity + focus + role + memory + founder ctx
worker: new Set([1, 2, 3, 4, 5, 6, 9, 10]), // + feedback + founder ctx
lead: new Set([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]), // all layers + founder ctx
coo: new Set([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]), // all layers + founder ctx + expanded budget
verifier: new Set([1, 2, 3, 4, 5, 6, 9, 10]), // same as worker + founder ctx
Comment on lines +63 to +67
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The comments for ROLE_SECTIONS should be updated to reflect that all roles now receive both layer 9 (founder-context) and layer 10 (founder-alignment).

Suggested change
scanner: new Set([1, 2, 3, 4, 5, 9, 10]), // identity + focus + role + memory + founder ctx
worker: new Set([1, 2, 3, 4, 5, 6, 9, 10]), // + feedback + founder ctx
lead: new Set([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]), // all layers + founder ctx
coo: new Set([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]), // all layers + founder ctx + expanded budget
verifier: new Set([1, 2, 3, 4, 5, 6, 9, 10]), // same as worker + founder ctx
scanner: new Set([1, 2, 3, 4, 5, 9, 10]), // identity + focus + role + memory + founder ctx + alignment
worker: new Set([1, 2, 3, 4, 5, 6, 9, 10]), // + feedback + founder ctx + alignment
lead: new Set([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]), // all layers + founder ctx + alignment
coo: new Set([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]), // all layers + founder ctx + alignment + expanded budget
verifier: new Set([1, 2, 3, 4, 5, 6, 9, 10]), // same as worker + founder ctx + alignment

};

// ── Agent Frontmatter ─────────────────────────────────────────────────
Expand Down Expand Up @@ -395,14 +411,23 @@ export function resolveContextRoleFromAgent(agentPath: string, agentName: string
* Gather context for agent execution.
*
* Layers flow general → particular (each adds a unique dimension):
* 1. company.md — Why (company identity, alignment)
* 2. priorities.md — Where (current focus, urgency)
* 3. goals.md — What (measurable targets)
* 4. agent.md — You (agent role, instructions)
* 5. state.md — Memory (continuity from last run)
* 6. feedback.md — Supporting (squad feedback)
* 7. daily-briefing — Supporting (org pulse, leads+coo only)
* 8. cross-squad — Supporting (learnings from other squads)
* 1. company.md — Why (company identity, alignment)
* 2. priorities.md — Where (current focus, urgency)
* 3. goals.md — What (measurable targets)
* 4. agent.md — You (agent role, instructions)
* 5. state.md — Memory (continuity from last run)
* 6. feedback.md — Supporting (squad feedback)
* 7. daily-briefing — Supporting (org pulse, leads+coo only)
* 8. cross-squad — Supporting (learnings from other squads)
* 9. founder-context.md — Live strategic state (universal, all roles)
* 10. founder-alignment.md — Per-squad contribution to current pipeline
*
* Layers 9 and 10 are injected FIRST in the prompt (LLMs pay most attention
* to the beginning of context) so squads align with the founder's live
* pipeline before processing any other layer. Both are auto-generated
* (e.g. by hq/.claude/hooks/founder-context-digest.py) which can also
* embed business-specific structural reference (Drive map, ERP architecture)
* directly into founder-context.md when relevant.
*
* SQUAD.md is NOT injected — it's metadata for the CLI (repo, agents, config).
* Missing files are skipped gracefully — no crashes on first run or new squads.
Expand All @@ -422,25 +447,31 @@ export function gatherSquadContext(
const sections: string[] = [];
let usedChars = 0;

/** Try to add a layer. Returns true if added, false if budget exceeded or not allowed. */
/** Try to add a layer. Returns true if added (possibly truncated), false if no budget left. */
function addLayer(layerNum: number, header: string, content: string, maxChars?: number): boolean {
if (!allowedSections.has(layerNum)) return false;
if (!content) return false;

let text = content;
const TRUNCATION_SUFFIX = '\n...';
const remaining = Math.max(0, budget - usedChars);
const cap = maxChars !== undefined ? Math.min(maxChars, remaining) : remaining;
if (text.length > cap) {
text = text.substring(0, cap) + '\n...';
}

if (usedChars + text.length > budget) {
if (remaining <= TRUNCATION_SUFFIX.length) {
// No room left for even a meaningful truncation
if (options.verbose) {
writeLine(` ${colors.dim}Context budget exhausted at layer ${layerNum} (${header})${RESET}`);
}
return false;
}

const cap = maxChars !== undefined ? Math.min(maxChars, remaining) : remaining;
let text = content;
if (text.length > cap) {
// Reserve TRUNCATION_SUFFIX bytes for the suffix so total fits exactly within cap
text = text.substring(0, cap - TRUNCATION_SUFFIX.length) + TRUNCATION_SUFFIX;
if (options.verbose) {
writeLine(` ${colors.dim}Layer ${layerNum} truncated to ${text.length}/${content.length} chars${RESET}`);
}
}

sections.push(`## ${header}\n${text}`);
usedChars += text.length;
return true;
Expand All @@ -454,8 +485,32 @@ export function gatherSquadContext(
// Put reference material last (company, agent definition).
// ═══════════════════════════════════════════════════════════════════

// ── L9: founder-context.md — Live strategic state (ACT-ALIGNED) ──
// Injected FIRST so agents see the founder's current pipeline before
// any squad-internal context. Auto-generated from interactive sessions,
// git activity, and open PRs/issues. Universal — all squads see this.
if (memoryDir) {
const founderContextFile = join(memoryDir, 'company', 'founder-context.md');
const content = safeRead(founderContextFile);
if (content) {
addLayer(9, 'Founder Context (live strategic state — read first)', content);
}
}

// ── L10: founder-alignment.md — How THIS squad contributes this cycle ──
// Per-squad translation of founder context into named, domain-specific
// contributions. Auto-generated alongside L9. Specific to this squadName.
if (memoryDir) {
const alignmentFile = join(memoryDir, squadName, 'founder-alignment.md');
const content = safeRead(alignmentFile);
if (content) {
addLayer(10, `Founder Alignment — ${squadName} (your contribution this cycle)`, content);
}
}

// ── L6: feedback.md — ACT ON THIS (corrections from last cycle) ──
// Injected FIRST so agents address feedback before anything else.
// Injected after founder context so corrections shape interpretation
// of the strategic state.
if (memoryDir) {
const feedbackFile = join(memoryDir, squadName, 'feedback.md');
const content = safeRead(feedbackFile);
Expand Down
Loading