* feat(#2792): namespace meta-skills retargeted at the post-#2790 surface This branch is now based on #2790's HEAD (the consolidation PR) instead of main, and every routing table targets the consolidated surface so a user routed by a namespace meta-skill never lands at a deleted / folded sub-skill. Cross-PR inconsistencies the original PR #2825 carried (vs #2790): - ns-ideate routed to gsd-note / gsd-add-todo / gsd-add-backlog / gsd-plant-seed → all folded into gsd-capture by #2790. Now routes to gsd-capture (the parent picks the mode from the user's intent). - ns-context routed to gsd-scan and gsd-intel → folded into gsd-map-codebase --fast / --query by #2790. Now routes to those flag forms. - ns-manage routed all workspace intent to gsd-list-workspaces (a list-only entry) → CR also flagged the over-narrow target. #2790 folds into gsd-workspace; routing now points there. - ns-workflow routed to gsd-research-phase → deleted outright by #2790. Removed. - ns-project routed to gsd-plan-milestone-gaps → deleted outright by #2790. Removed. - None of the namespaces previously surfaced #2790's new consolidated skills (gsd-capture, gsd-phase, gsd-config, gsd-workspace, gsd-progress). All five are now reachable through the routers. - extract_learnings → extract-learnings (canonicalized by #2858). Defect fixes within the namespace skills: - Hyphen-form `name:` (gsd-workflow, …) per the canonical naming contract — the colon-form addressed CR's drift complaint. - `Skill` added to allowed-tools on every router. The body instructs "Invoke the matched skill directly using the Skill tool" — without Skill in the permission list the meta-skill cannot route at all. New regression guard in tests/enh-2792-namespace-skills.test.cjs: every gsd-* token in any namespace router's table column resolves to a surviving commands/gsd/*.md file (or to a known consolidated parent for flag-form targets like gsd-map-codebase --fast). This single test would have caught every dead-end route the original PR shipped with. Skill-count cap in tests/enh-2790-skill-consolidation.test.cjs now filters out ns-*.md from its <= 63 cap. Namespace routers are descriptor-only entries, not part of the consolidation surface that cap is policing — they have their own contract in tests/enh-2792-namespace-skills.test.cjs. INVENTORY.md gains a "Namespace Meta-Skills" section with the 6 router rows; INVENTORY-MANIFEST.json gains 6 entries; the headline count moves 59 → 65 to match. Out of scope for this rebase: the gsd-health --context flag (PR #2825 advertised the contract but didn't implement it). That's a separate feature concern and is left untouched here. 5908/5908 on `npm test`. * feat(#2792): implement gsd-health --context utilization guard The original PR #2825 advertised a `--context` flag on gsd-health with a 60%/70% utilization threshold table but never implemented the workflow logic — CR caught it as a contract leak, the rebase deferred it. This commit closes the gap with TDD red/green/refactor. Math layer (pure): - get-shit-done/bin/lib/context-utilization.cjs classifyContextUtilization(tokensUsed, contextWindow) → { percent, state } State boundaries use the exact ratio: < 60% healthy / 60–70% warning / ≥ 70% critical (fracture point) Display percent rounded for humans. Throws TypeError on non-integer or out-of-range inputs. - STATES = Object.freeze({ HEALTHY, WARNING, CRITICAL }) exported so callers reference the names by symbol, not by literal string. SDK CLI integration: - get-shit-done/bin/gsd-tools.cjs `validate context --tokens-used N --context-window M [--json]` routes to the classifier, owns the recommendation copy (the classifier intentionally does not — keeps the renderer free to evolve without touching the math layer or its tests), and uses core.output's rawValue path for the sync-flush guarantee. - sdk/src/query/validate.ts + sdk/src/query/index.ts TypeScript validateContext handler registered at 'validate.context' and 'validate context'. Mirrors the CJS classifier inline (15 lines of arithmetic; not worth a shared cross-language module). User-facing wiring: - commands/gsd/health.md frontmatter advertises --context, body documents the three-state threshold table. - get-shit-done/workflows/health.md adds a `context_check` step that's reached only when --context is set. Step calls `gsd-sdk query validate.context` with self-reported tokensUsed and contextWindow, prints the SDK output verbatim, and ends. Includes a TEXT_MODE plain-text fallback for non-Claude runtimes per #2012. Tests: - tests/context-utilization.test.cjs (17 tests) — pure-function contract: state thresholds at every boundary, percent rounding, input validation, return-shape (no recommendation field — that's the renderer's job). - tests/validate-context.test.cjs (9 tests) — SDK CLI plumbing: arg parsing errors, JSON vs human rendering, recommendation copy pinned per state. - tests/enh-2792-namespace-skills.test.cjs (4 new tests) — markdown contract: --context advertised in argument-hint, threshold table in command body, context_check step exists in workflow, step invokes gsd-sdk query validate.context with both flags. Inventory bookkeeping: - docs/INVENTORY.md "CLI Modules" 31 → 32; new row for context-utilization.cjs. - docs/INVENTORY-MANIFEST.json mirror. 5939/5939 on `npm test`.
This commit is contained in:
@@ -792,8 +792,48 @@ async function runCommand(command, args, cwd, raw, defaultValue) {
|
||||
verify.cmdValidateHealth(cwd, { repair: repairFlag, backfill: backfillFlag }, raw);
|
||||
} else if (subcommand === 'agents') {
|
||||
verify.cmdValidateAgents(cwd, raw);
|
||||
} else if (subcommand === 'context') {
|
||||
// The model self-reports tokensUsed and contextWindow — the SDK has
|
||||
// no privileged access to either. Recommendation copy lives here
|
||||
// (the renderer), not in the classifier, so it can change without
|
||||
// re-validating the math layer.
|
||||
const opts = parseNamedArgs(args, ['tokens-used', 'context-window']);
|
||||
if (opts['tokens-used'] === null) {
|
||||
error('--tokens-used <integer> is required for `validate context`');
|
||||
break;
|
||||
}
|
||||
if (opts['context-window'] === null) {
|
||||
error('--context-window <integer> is required for `validate context`');
|
||||
break;
|
||||
}
|
||||
const { classifyContextUtilization, STATES } = require('./lib/context-utilization.cjs');
|
||||
const RECOMMENDATIONS = {
|
||||
[STATES.HEALTHY]: null,
|
||||
[STATES.WARNING]: 'Context is approaching the fracture zone — consider /gsd-thread to continue in a fresh window.',
|
||||
[STATES.CRITICAL]: 'Reasoning quality may degrade past 70% utilization (fracture point). Run /gsd-thread now to preserve output quality.',
|
||||
};
|
||||
let classified;
|
||||
try {
|
||||
classified = classifyContextUtilization(Number(opts['tokens-used']), Number(opts['context-window']));
|
||||
} catch (e) {
|
||||
// Translate the classifier's TypeError into a CLI-shaped error
|
||||
// message that names the offending flag.
|
||||
const flag = /tokensUsed/.test(e.message) ? '--tokens-used' : '--context-window';
|
||||
error(`${flag} must be a non-negative integer (window > 0), got the values supplied`);
|
||||
break;
|
||||
}
|
||||
const result = { ...classified, recommendation: RECOMMENDATIONS[classified.state] };
|
||||
if (args.includes('--json')) {
|
||||
core.output(result, raw);
|
||||
} else {
|
||||
const lines = [`Context utilization: ${result.percent}% (${result.state})`];
|
||||
if (result.recommendation) lines.push(result.recommendation);
|
||||
// Use core.output's rawValue path for the sync-flush guarantee
|
||||
// — process.stdout.write can be truncated on process exit.
|
||||
core.output(result, true, lines.join('\n'));
|
||||
}
|
||||
} else {
|
||||
error('Unknown validate subcommand. Available: consistency, health, agents');
|
||||
error('Unknown validate subcommand. Available: consistency, health, agents, context');
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
47
get-shit-done/bin/lib/context-utilization.cjs
Normal file
47
get-shit-done/bin/lib/context-utilization.cjs
Normal file
@@ -0,0 +1,47 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Context-utilization classifier for `gsd-health --context`.
|
||||
*
|
||||
* Pure function. Callers pass tokensUsed + contextWindow; the
|
||||
* classifier returns the percent and one of three states. Recommendation
|
||||
* strings are NOT in this module — formatting is the renderer's job
|
||||
* (see `validate context` in gsd-tools.cjs). That separation lets the
|
||||
* copy change without touching this module's tests.
|
||||
*
|
||||
* Thresholds:
|
||||
* < 60% healthy no action
|
||||
* 60–70% warning approaching the fracture zone
|
||||
* ≥ 70% critical reasoning quality may degrade
|
||||
*
|
||||
* State boundaries use the exact ratio. The displayed `percent` is
|
||||
* rounded for human reading and may differ from the boundary by ±1 in
|
||||
* edge cases (e.g. 59.999% displays as 60 but classifies as healthy).
|
||||
*/
|
||||
|
||||
const STATES = Object.freeze({
|
||||
HEALTHY: 'healthy',
|
||||
WARNING: 'warning',
|
||||
CRITICAL: 'critical',
|
||||
});
|
||||
|
||||
function classifyContextUtilization(tokensUsed, contextWindow) {
|
||||
if (!Number.isInteger(tokensUsed) || tokensUsed < 0) {
|
||||
throw new TypeError(`tokensUsed must be a non-negative integer, got: ${tokensUsed} (${typeof tokensUsed})`);
|
||||
}
|
||||
if (!Number.isInteger(contextWindow) || contextWindow <= 0) {
|
||||
throw new TypeError(`contextWindow must be a positive integer, got: ${contextWindow} (${typeof contextWindow})`);
|
||||
}
|
||||
|
||||
const ratio = Math.min(tokensUsed / contextWindow, 1);
|
||||
const percent = Math.min(Math.round(ratio * 100), 100);
|
||||
|
||||
let state;
|
||||
if (ratio < 0.60) state = STATES.HEALTHY;
|
||||
else if (ratio < 0.70) state = STATES.WARNING;
|
||||
else state = STATES.CRITICAL;
|
||||
|
||||
return { percent, state };
|
||||
}
|
||||
|
||||
module.exports = { classifyContextUtilization, STATES };
|
||||
@@ -11,18 +11,53 @@ Read all files referenced by the invoking prompt's execution_context before star
|
||||
<step name="parse_args">
|
||||
**Parse arguments:**
|
||||
|
||||
Check if `--repair` or `--backfill` flags are present in the command arguments.
|
||||
Check if `--repair`, `--backfill`, or `--context` flags are present in the command arguments.
|
||||
|
||||
```
|
||||
REPAIR_FLAG=""
|
||||
BACKFILL_FLAG=""
|
||||
CONTEXT_MODE=""
|
||||
if arguments contain "--repair"; then
|
||||
REPAIR_FLAG="--repair"
|
||||
fi
|
||||
if arguments contain "--backfill"; then
|
||||
BACKFILL_FLAG="--backfill"
|
||||
fi
|
||||
if arguments contain "--context"; then
|
||||
CONTEXT_MODE="true"
|
||||
fi
|
||||
```
|
||||
|
||||
If `CONTEXT_MODE` is set, jump to the `context_check` step and skip the
|
||||
integrity validation steps. The two modes are orthogonal — context utilization
|
||||
has nothing to do with `.planning/` directory health.
|
||||
</step>
|
||||
|
||||
<step name="context_check">
|
||||
**Run only when `--context` is set.**
|
||||
|
||||
The model running this workflow self-reports the current session's
|
||||
approximate `tokensUsed` and the active model's `contextWindow`. Use the values
|
||||
visible in your runtime (Claude Code's `/context` slash command output, or the
|
||||
model's own session telemetry). If the runtime exposes neither, prompt the user
|
||||
once via AskUserQuestion for both numbers.
|
||||
|
||||
**TEXT_MODE fallback:** when `text_mode` is true (config or `--text` flag) the
|
||||
runtime is non-Claude (Codex, Gemini, etc.) and `AskUserQuestion` is not
|
||||
available — replace the prompt with a plain-text two-question sequence
|
||||
("Approximate tokens used? Context window size?") and read the answers as
|
||||
plain text from the user's response.
|
||||
|
||||
```bash
|
||||
gsd-sdk query validate.context \
|
||||
--tokens-used "$TOKENS_USED" \
|
||||
--context-window "$CONTEXT_WINDOW"
|
||||
```
|
||||
|
||||
The query prints a one-line status (`Context utilization: NN% (state)`) plus
|
||||
a recommendation line for the warning and critical states. Print the SDK
|
||||
output verbatim and end the workflow — do **not** mix in `.planning/`
|
||||
health output, the two modes are independent diagnostics.
|
||||
</step>
|
||||
|
||||
<step name="run_health_check">
|
||||
|
||||
Reference in New Issue
Block a user