@geraldmaron/construct 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +69 -0
- package/LICENSE +21 -0
- package/README.md +344 -0
- package/agents/prompts/cx-accessibility.md +29 -0
- package/agents/prompts/cx-ai-engineer.md +31 -0
- package/agents/prompts/cx-architect.md +40 -0
- package/agents/prompts/cx-business-strategist.md +25 -0
- package/agents/prompts/cx-data-analyst.md +30 -0
- package/agents/prompts/cx-data-engineer.md +32 -0
- package/agents/prompts/cx-debugger.md +27 -0
- package/agents/prompts/cx-designer.md +33 -0
- package/agents/prompts/cx-devil-advocate.md +36 -0
- package/agents/prompts/cx-docs-keeper.md +50 -0
- package/agents/prompts/cx-engineer.md +36 -0
- package/agents/prompts/cx-evaluator.md +26 -0
- package/agents/prompts/cx-explorer.md +38 -0
- package/agents/prompts/cx-legal-compliance.md +25 -0
- package/agents/prompts/cx-operations.md +28 -0
- package/agents/prompts/cx-orchestrator.md +30 -0
- package/agents/prompts/cx-platform-engineer.md +25 -0
- package/agents/prompts/cx-product-manager.md +30 -0
- package/agents/prompts/cx-qa.md +42 -0
- package/agents/prompts/cx-rd-lead.md +25 -0
- package/agents/prompts/cx-release-manager.md +31 -0
- package/agents/prompts/cx-researcher.md +31 -0
- package/agents/prompts/cx-reviewer.md +36 -0
- package/agents/prompts/cx-security.md +37 -0
- package/agents/prompts/cx-sre.md +26 -0
- package/agents/prompts/cx-test-automation.md +35 -0
- package/agents/prompts/cx-trace-reviewer.md +76 -0
- package/agents/prompts/cx-ux-researcher.md +30 -0
- package/agents/registry.json +879 -0
- package/agents/teams.json +94 -0
- package/bin/construct +613 -0
- package/commands/build/feature.md +21 -0
- package/commands/build/fix.md +20 -0
- package/commands/design/access.md +19 -0
- package/commands/design/flow.md +16 -0
- package/commands/design/ui.md +18 -0
- package/commands/measure/experiment.md +18 -0
- package/commands/measure/metrics.md +18 -0
- package/commands/measure/results.md +18 -0
- package/commands/plan/api.md +19 -0
- package/commands/plan/challenge.md +18 -0
- package/commands/plan/decide.md +20 -0
- package/commands/plan/feature.md +49 -0
- package/commands/plan/requirements.md +24 -0
- package/commands/remember/context.md +26 -0
- package/commands/remember/handoff.md +19 -0
- package/commands/remember/runbook.md +20 -0
- package/commands/review/code.md +21 -0
- package/commands/review/quality.md +19 -0
- package/commands/review/security.md +16 -0
- package/commands/ship/ready.md +19 -0
- package/commands/ship/release.md +19 -0
- package/commands/ship/status.md +18 -0
- package/commands/understand/docs.md +22 -0
- package/commands/understand/research.md +22 -0
- package/commands/understand/this.md +23 -0
- package/commands/understand/why.md +18 -0
- package/commands/work/clean.md +32 -0
- package/commands/work/drive.md +69 -0
- package/commands/work/optimize-prompts.md +46 -0
- package/commands/work/parallel-review.md +40 -0
- package/db/migrations/001_init.sql +38 -0
- package/langfuse/docker-compose.yml +82 -0
- package/lib/audit-skills.mjs +118 -0
- package/lib/auto-docs.mjs +293 -0
- package/lib/cli-commands.mjs +409 -0
- package/lib/codex-config.mjs +114 -0
- package/lib/comment-lint.mjs +191 -0
- package/lib/completions.mjs +170 -0
- package/lib/context-state.mjs +99 -0
- package/lib/cost.mjs +213 -0
- package/lib/distill.mjs +403 -0
- package/lib/efficiency.mjs +139 -0
- package/lib/env-config.mjs +53 -0
- package/lib/eval-harness.mjs +59 -0
- package/lib/features.mjs +209 -0
- package/lib/headhunt.mjs +597 -0
- package/lib/hooks/adaptive-lint.mjs +112 -0
- package/lib/hooks/agent-tracker.mjs +50 -0
- package/lib/hooks/bootstrap-guard.mjs +90 -0
- package/lib/hooks/comment-lint.mjs +26 -0
- package/lib/hooks/config-protection.mjs +52 -0
- package/lib/hooks/console-warn.mjs +43 -0
- package/lib/hooks/context-window-recovery.mjs +90 -0
- package/lib/hooks/continuation-enforcer.mjs +72 -0
- package/lib/hooks/dep-audit.mjs +155 -0
- package/lib/hooks/drive-guard.mjs +89 -0
- package/lib/hooks/edit-accumulator.mjs +36 -0
- package/lib/hooks/edit-error-recovery.mjs +46 -0
- package/lib/hooks/edit-guard.mjs +109 -0
- package/lib/hooks/env-check.mjs +80 -0
- package/lib/hooks/guard-bash.mjs +83 -0
- package/lib/hooks/mcp-audit.mjs +57 -0
- package/lib/hooks/mcp-health-check.mjs +51 -0
- package/lib/hooks/mcp-task-scope.mjs +47 -0
- package/lib/hooks/model-fallback.mjs +105 -0
- package/lib/hooks/pre-compact.mjs +212 -0
- package/lib/hooks/pre-push-gate.mjs +129 -0
- package/lib/hooks/read-tracker.mjs +129 -0
- package/lib/hooks/registry-sync.mjs +23 -0
- package/lib/hooks/scan-secrets.mjs +76 -0
- package/lib/hooks/session-start.mjs +95 -0
- package/lib/hooks/stop-notify.mjs +191 -0
- package/lib/hooks/stop-typecheck.mjs +83 -0
- package/lib/hooks/task-completed-guard.mjs +43 -0
- package/lib/hooks/teammate-idle-guard.mjs +54 -0
- package/lib/hooks/workflow-guard.mjs +62 -0
- package/lib/host-capabilities.mjs +123 -0
- package/lib/init-docs.mjs +382 -0
- package/lib/mcp/server.mjs +1123 -0
- package/lib/mcp-catalog.json +243 -0
- package/lib/mcp-manager.mjs +433 -0
- package/lib/mcp-platform-config.mjs +104 -0
- package/lib/model-router.mjs +810 -0
- package/lib/opencode-config.mjs +44 -0
- package/lib/opencode-runtime-plugin.mjs +909 -0
- package/lib/opencode-telemetry.mjs +433 -0
- package/lib/orchestration-policy.mjs +258 -0
- package/lib/prompt-composer.mjs +196 -0
- package/lib/prompt-metadata.mjs +68 -0
- package/lib/review.mjs +429 -0
- package/lib/role-preload.mjs +52 -0
- package/lib/schemas/decision.json +50 -0
- package/lib/schemas/implementation.json +51 -0
- package/lib/schemas/review-report.json +32 -0
- package/lib/schemas/test-report.json +43 -0
- package/lib/server/index.mjs +334 -0
- package/lib/server/static/app.js +841 -0
- package/lib/server/static/index.html +820 -0
- package/lib/service-manager.mjs +225 -0
- package/lib/setup.mjs +319 -0
- package/lib/status.mjs +707 -0
- package/lib/storage/backend.mjs +44 -0
- package/lib/storage/embeddings.mjs +70 -0
- package/lib/storage/hybrid-query.mjs +184 -0
- package/lib/storage/sql-store.mjs +55 -0
- package/lib/storage/state-source.mjs +101 -0
- package/lib/storage/sync.mjs +164 -0
- package/lib/storage/vector-store.mjs +59 -0
- package/lib/telemetry/backends/langfuse.mjs +58 -0
- package/lib/telemetry/backends/noop.mjs +16 -0
- package/lib/telemetry/langfuse-ingest.mjs +108 -0
- package/lib/telemetry/langfuse-model-sync.mjs +270 -0
- package/lib/telemetry/team-rollup.mjs +143 -0
- package/lib/toolkit-env.mjs +30 -0
- package/lib/validator.mjs +181 -0
- package/lib/workflow-state.mjs +794 -0
- package/package.json +53 -0
- package/personas/construct.md +94 -0
- package/platforms/claude/CLAUDE.md +30 -0
- package/platforms/claude/settings.template.json +472 -0
- package/platforms/opencode/config.template.json +65 -0
- package/platforms/opencode/plugins/construct-fallback.js +5 -0
- package/platforms/opencode/sync-config.mjs +69 -0
- package/rules/common/agents.md +55 -0
- package/rules/common/code-review.md +129 -0
- package/rules/common/coding-style.md +95 -0
- package/rules/common/comments.md +139 -0
- package/rules/common/cx-agent-routing.md +61 -0
- package/rules/common/cx-skill-routing.md +11 -0
- package/rules/common/development-workflow.md +49 -0
- package/rules/common/git-workflow.md +29 -0
- package/rules/common/hooks.md +35 -0
- package/rules/common/patterns.md +36 -0
- package/rules/common/performance.md +71 -0
- package/rules/common/security.md +34 -0
- package/rules/common/testing.md +62 -0
- package/rules/golang/coding-style.md +37 -0
- package/rules/golang/hooks.md +22 -0
- package/rules/golang/patterns.md +50 -0
- package/rules/golang/security.md +39 -0
- package/rules/golang/testing.md +36 -0
- package/rules/python/coding-style.md +47 -0
- package/rules/python/hooks.md +24 -0
- package/rules/python/patterns.md +44 -0
- package/rules/python/security.md +35 -0
- package/rules/python/testing.md +43 -0
- package/rules/swift/coding-style.md +52 -0
- package/rules/swift/hooks.md +25 -0
- package/rules/swift/patterns.md +71 -0
- package/rules/swift/security.md +38 -0
- package/rules/swift/testing.md +50 -0
- package/rules/typescript/coding-style.md +204 -0
- package/rules/typescript/hooks.md +27 -0
- package/rules/typescript/patterns.md +57 -0
- package/rules/typescript/security.md +33 -0
- package/rules/typescript/testing.md +23 -0
- package/rules/web/coding-style.md +101 -0
- package/rules/web/design-quality.md +68 -0
- package/rules/web/hooks.md +125 -0
- package/rules/web/patterns.md +84 -0
- package/rules/web/performance.md +69 -0
- package/rules/web/security.md +62 -0
- package/rules/web/testing.md +60 -0
- package/rules/zh/README.md +113 -0
- package/rules/zh/agents.md +55 -0
- package/rules/zh/code-review.md +129 -0
- package/rules/zh/coding-style.md +53 -0
- package/rules/zh/development-workflow.md +49 -0
- package/rules/zh/git-workflow.md +29 -0
- package/rules/zh/hooks.md +35 -0
- package/rules/zh/patterns.md +36 -0
- package/rules/zh/performance.md +60 -0
- package/rules/zh/security.md +34 -0
- package/rules/zh/testing.md +34 -0
- package/skills/ai/agent-dev.md +102 -0
- package/skills/ai/llm-security.md +105 -0
- package/skills/ai/ml-ops.md +169 -0
- package/skills/ai/orchestration-workflow.md +87 -0
- package/skills/ai/prompt-and-eval.md +114 -0
- package/skills/ai/prompt-optimizer.md +114 -0
- package/skills/ai/rag-system.md +104 -0
- package/skills/architecture/api-design.md +100 -0
- package/skills/architecture/caching.md +101 -0
- package/skills/architecture/cloud-native.md +97 -0
- package/skills/architecture/message-queue.md +100 -0
- package/skills/architecture/security-arch.md +105 -0
- package/skills/development/cpp.md +127 -0
- package/skills/development/go.md +129 -0
- package/skills/development/java.md +134 -0
- package/skills/development/kotlin.md +140 -0
- package/skills/development/mobile-crossplatform.md +144 -0
- package/skills/development/python.md +109 -0
- package/skills/development/rust.md +127 -0
- package/skills/development/shell.md +127 -0
- package/skills/development/swift.md +129 -0
- package/skills/development/typescript.md +129 -0
- package/skills/devops/ci-cd.md +108 -0
- package/skills/devops/containerization.md +106 -0
- package/skills/devops/cost-optimization.md +105 -0
- package/skills/devops/data-engineering.md +181 -0
- package/skills/devops/database.md +95 -0
- package/skills/devops/dependency-management.md +91 -0
- package/skills/devops/devsecops.md +96 -0
- package/skills/devops/git-workflow.md +100 -0
- package/skills/devops/incident-response.md +167 -0
- package/skills/devops/monorepo.md +87 -0
- package/skills/devops/observability.md +103 -0
- package/skills/devops/performance.md +104 -0
- package/skills/devops/testing.md +104 -0
- package/skills/docs/adr-workflow.md +32 -0
- package/skills/docs/backlog-proposal-workflow.md +19 -0
- package/skills/docs/customer-profile-workflow.md +22 -0
- package/skills/docs/evidence-ingest-workflow.md +23 -0
- package/skills/docs/init-docs.md +160 -0
- package/skills/docs/init-project.md +50 -0
- package/skills/docs/prd-workflow.md +57 -0
- package/skills/docs/prfaq-workflow.md +25 -0
- package/skills/docs/product-intelligence-review.md +22 -0
- package/skills/docs/product-intelligence-workflow.md +62 -0
- package/skills/docs/product-signal-workflow.md +27 -0
- package/skills/docs/research-workflow.md +28 -0
- package/skills/docs/runbook-workflow.md +24 -0
- package/skills/exploration/repo-map.md +297 -0
- package/skills/frameworks/django.md +159 -0
- package/skills/frameworks/nextjs.md +148 -0
- package/skills/frameworks/react.md +132 -0
- package/skills/frameworks/spring-boot.md +165 -0
- package/skills/frontend-design/accessibility.md +153 -0
- package/skills/frontend-design/component-patterns.md +111 -0
- package/skills/frontend-design/engineering.md +122 -0
- package/skills/frontend-design/state-management.md +118 -0
- package/skills/frontend-design/ui-aesthetics.md +102 -0
- package/skills/frontend-design/ux-principles.md +126 -0
- package/skills/quality-gates/review-work.md +90 -0
- package/skills/quality-gates/verify-change.md +94 -0
- package/skills/quality-gates/verify-module.md +96 -0
- package/skills/quality-gates/verify-quality.md +93 -0
- package/skills/quality-gates/verify-security.md +95 -0
- package/skills/roles/architect.ai-systems.md +37 -0
- package/skills/roles/architect.data.md +37 -0
- package/skills/roles/architect.enterprise.md +37 -0
- package/skills/roles/architect.integration.md +37 -0
- package/skills/roles/architect.md +68 -0
- package/skills/roles/architect.platform.md +37 -0
- package/skills/roles/data-analyst.experiment.md +37 -0
- package/skills/roles/data-analyst.md +68 -0
- package/skills/roles/data-analyst.product-intelligence.md +37 -0
- package/skills/roles/data-analyst.product.md +37 -0
- package/skills/roles/data-analyst.telemetry.md +37 -0
- package/skills/roles/data-engineer.pipeline.md +37 -0
- package/skills/roles/data-engineer.vector-retrieval.md +37 -0
- package/skills/roles/data-engineer.warehouse.md +37 -0
- package/skills/roles/debugger.md +68 -0
- package/skills/roles/designer.accessibility.md +43 -0
- package/skills/roles/designer.md +68 -0
- package/skills/roles/engineer.ai.md +49 -0
- package/skills/roles/engineer.data.md +49 -0
- package/skills/roles/engineer.md +85 -0
- package/skills/roles/engineer.platform.md +49 -0
- package/skills/roles/operator.docs.md +43 -0
- package/skills/roles/operator.md +68 -0
- package/skills/roles/operator.release.md +43 -0
- package/skills/roles/operator.sre.md +43 -0
- package/skills/roles/orchestrator.md +66 -0
- package/skills/roles/product-manager.ai-product.md +37 -0
- package/skills/roles/product-manager.business-strategy.md +43 -0
- package/skills/roles/product-manager.enterprise.md +37 -0
- package/skills/roles/product-manager.growth.md +37 -0
- package/skills/roles/product-manager.md +67 -0
- package/skills/roles/product-manager.platform.md +37 -0
- package/skills/roles/product-manager.product.md +37 -0
- package/skills/roles/qa.ai-eval.md +37 -0
- package/skills/roles/qa.api-contract.md +37 -0
- package/skills/roles/qa.data-pipeline.md +37 -0
- package/skills/roles/qa.md +68 -0
- package/skills/roles/qa.test-automation.md +49 -0
- package/skills/roles/qa.web-ui.md +37 -0
- package/skills/roles/researcher.explorer.md +43 -0
- package/skills/roles/researcher.md +68 -0
- package/skills/roles/researcher.ux.md +43 -0
- package/skills/roles/reviewer.devil-advocate.md +43 -0
- package/skills/roles/reviewer.evaluator.md +43 -0
- package/skills/roles/reviewer.md +68 -0
- package/skills/roles/reviewer.trace.md +43 -0
- package/skills/roles/security.ai.md +37 -0
- package/skills/roles/security.appsec.md +37 -0
- package/skills/roles/security.cloud.md +37 -0
- package/skills/roles/security.legal-compliance.md +49 -0
- package/skills/roles/security.md +68 -0
- package/skills/roles/security.privacy.md +37 -0
- package/skills/roles/security.supply-chain.md +37 -0
- package/skills/routing.md +139 -0
- package/skills/security/blue-team.md +83 -0
- package/skills/security/code-audit.md +94 -0
- package/skills/security/pentest.md +84 -0
- package/skills/security/red-team.md +81 -0
- package/skills/security/threat-intel.md +89 -0
- package/skills/security/vuln-research.md +87 -0
- package/skills/utility/clean-code.md +165 -0
- package/sync-agents.mjs +899 -0
- package/templates/docs/adr.md +27 -0
- package/templates/docs/backlog-proposal.md +26 -0
- package/templates/docs/customer-profile.md +34 -0
- package/templates/docs/evidence-brief.md +37 -0
- package/templates/docs/incident-report.md +46 -0
- package/templates/docs/memo.md +27 -0
- package/templates/docs/meta-prd.md +56 -0
- package/templates/docs/one-pager.md +24 -0
- package/templates/docs/prd-business.md +61 -0
- package/templates/docs/prd-platform.md +62 -0
- package/templates/docs/prd.md +47 -0
- package/templates/docs/prfaq.md +31 -0
- package/templates/docs/product-intelligence-report.md +31 -0
- package/templates/docs/research-brief.md +29 -0
- package/templates/docs/rfc-platform.md +60 -0
- package/templates/docs/rfc.md +46 -0
- package/templates/docs/runbook.md +33 -0
- package/templates/docs/signal-brief.md +25 -0
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* lib/hooks/adaptive-lint.mjs — Adaptive lint escalation hook — tracks repeated violations and escalates severity.
|
|
4
|
+
*
|
|
5
|
+
* Runs as PostToolUse after Edit/Write. Reads the lint history log and escalates warnings to errors for files with repeated violations. Writes updated history to ~/.cx/lint-history.json.
|
|
6
|
+
*/
|
|
7
|
+
import { execSync } from 'child_process';
|
|
8
|
+
import { existsSync, readFileSync } from 'fs';
|
|
9
|
+
import { dirname, extname, join } from 'path';
|
|
10
|
+
|
|
11
|
+
function findUp(startDir, filename) {
|
|
12
|
+
let dir = startDir;
|
|
13
|
+
const root = '/';
|
|
14
|
+
while (dir !== root) {
|
|
15
|
+
const candidate = join(dir, filename);
|
|
16
|
+
if (existsSync(candidate)) return candidate;
|
|
17
|
+
const parent = dirname(dir);
|
|
18
|
+
if (parent === dir) break;
|
|
19
|
+
dir = parent;
|
|
20
|
+
}
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function hasDep(pkgPath, dep) {
|
|
25
|
+
try {
|
|
26
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
|
|
27
|
+
return !!(
|
|
28
|
+
pkg.dependencies?.[dep] ||
|
|
29
|
+
pkg.devDependencies?.[dep] ||
|
|
30
|
+
pkg.peerDependencies?.[dep]
|
|
31
|
+
);
|
|
32
|
+
} catch {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function run(cmd) {
|
|
38
|
+
try {
|
|
39
|
+
execSync(cmd, { stdio: 'pipe' });
|
|
40
|
+
return true;
|
|
41
|
+
} catch {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function isOnPath(bin) {
|
|
47
|
+
try {
|
|
48
|
+
execSync(`command -v ${bin}`, { stdio: 'pipe' });
|
|
49
|
+
return true;
|
|
50
|
+
} catch {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const filePath = process.env.TOOL_INPUT_FILE_PATH;
|
|
56
|
+
|
|
57
|
+
if (!filePath) process.exit(0);
|
|
58
|
+
|
|
59
|
+
const ext = extname(filePath).toLowerCase();
|
|
60
|
+
const fileDir = dirname(filePath);
|
|
61
|
+
const quoted = JSON.stringify(filePath);
|
|
62
|
+
|
|
63
|
+
let acted = false;
|
|
64
|
+
|
|
65
|
+
const pkgPath = findUp(fileDir, 'package.json');
|
|
66
|
+
if (pkgPath) {
|
|
67
|
+
if (hasDep(pkgPath, 'eslint') || hasDep(pkgPath, '@eslint/js')) {
|
|
68
|
+
const ok = run(`npx eslint --fix ${quoted} 2>/dev/null`);
|
|
69
|
+
if (!ok) process.stderr.write(`[adaptive-lint] eslint not available or failed on ${filePath}\n`);
|
|
70
|
+
acted = true;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (!acted && hasDep(pkgPath, 'prettier')) {
|
|
74
|
+
const ok = run(`npx prettier --write ${quoted} 2>/dev/null`);
|
|
75
|
+
if (!ok) process.stderr.write(`[adaptive-lint] prettier not available or failed on ${filePath}\n`);
|
|
76
|
+
acted = true;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (!acted) {
|
|
81
|
+
const hasPyproject = !!findUp(fileDir, 'pyproject.toml');
|
|
82
|
+
const hasSetupPy = !!findUp(fileDir, 'setup.py');
|
|
83
|
+
if (hasPyproject || hasSetupPy) {
|
|
84
|
+
if (isOnPath('ruff')) {
|
|
85
|
+
const ok = run(`ruff check --fix ${quoted} 2>/dev/null`);
|
|
86
|
+
if (!ok) process.stderr.write(`[adaptive-lint] ruff failed on ${filePath}\n`);
|
|
87
|
+
} else {
|
|
88
|
+
process.stderr.write(`[adaptive-lint] ruff not on PATH — skipping Python lint for ${filePath}\n`);
|
|
89
|
+
}
|
|
90
|
+
acted = true;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (!acted && ext === '.go') {
|
|
95
|
+
if (isOnPath('gofmt')) {
|
|
96
|
+
const ok = run(`gofmt -w ${quoted}`);
|
|
97
|
+
if (!ok) process.stderr.write(`[adaptive-lint] gofmt failed on ${filePath}\n`);
|
|
98
|
+
} else {
|
|
99
|
+
process.stderr.write(`[adaptive-lint] gofmt not on PATH — skipping Go format for ${filePath}\n`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (!acted && ext === '.rs') {
|
|
104
|
+
if (isOnPath('rustfmt')) {
|
|
105
|
+
const ok = run(`rustfmt ${quoted}`);
|
|
106
|
+
if (!ok) process.stderr.write(`[adaptive-lint] rustfmt failed on ${filePath}\n`);
|
|
107
|
+
} else {
|
|
108
|
+
process.stderr.write(`[adaptive-lint] rustfmt not on PATH — skipping Rust format for ${filePath}\n`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
process.exit(0);
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* lib/hooks/agent-tracker.mjs — Agent task lifecycle hook — tracks task start, completion, and handoffs.
|
|
4
|
+
*
|
|
5
|
+
* Runs as PostToolUse after Agent tool calls. Records agent invocations and their outcomes to ~/.cx/agent-log.json for telemetry and performance review.
|
|
6
|
+
*/
|
|
7
|
+
// PostToolUse(Task) — records the last dispatched subagent to ~/.cx/last-agent.json.
|
|
8
|
+
// stop-notify.mjs reads this to attribute per-turn token costs to the dispatching agent.
|
|
9
|
+
import { readFileSync, writeFileSync, mkdirSync } from 'fs';
|
|
10
|
+
import { join } from 'path';
|
|
11
|
+
import { homedir } from 'os';
|
|
12
|
+
|
|
13
|
+
let input = {};
|
|
14
|
+
try { input = JSON.parse(readFileSync(0, 'utf8')); } catch { process.exit(0); }
|
|
15
|
+
|
|
16
|
+
const toolName = input?.tool_name || '';
|
|
17
|
+
if (toolName !== 'Task') process.exit(0);
|
|
18
|
+
|
|
19
|
+
const toolInput = input?.tool_input || {};
|
|
20
|
+
const cwd = input?.cwd || process.cwd();
|
|
21
|
+
|
|
22
|
+
// Extract agent identity from the Task tool input.
|
|
23
|
+
// Claude Code passes subagent_type or description in tool_input.
|
|
24
|
+
const agentType = toolInput?.subagent_type || toolInput?.agent || null;
|
|
25
|
+
const description = toolInput?.description || toolInput?.prompt || '';
|
|
26
|
+
|
|
27
|
+
// Prefer subagent_type (e.g. "cx-engineer"), fall back to first cx-* token in description.
|
|
28
|
+
let agentName = agentType;
|
|
29
|
+
if (!agentName) {
|
|
30
|
+
const descMatch = /^(cx-[a-z-]+|construct)/i.exec(description.trim());
|
|
31
|
+
agentName = descMatch ? descMatch[1].toLowerCase() : 'subagent';
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Load active task key from workflow
|
|
35
|
+
let taskKey = null;
|
|
36
|
+
try {
|
|
37
|
+
const wf = JSON.parse(readFileSync(join(cwd, '.cx', 'workflow.json'), 'utf8'));
|
|
38
|
+
taskKey = wf.currentTaskKey || null;
|
|
39
|
+
} catch { /* no workflow */ }
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
const home = homedir();
|
|
43
|
+
mkdirSync(join(home, '.cx'), { recursive: true });
|
|
44
|
+
writeFileSync(
|
|
45
|
+
join(home, '.cx', 'last-agent.json'),
|
|
46
|
+
JSON.stringify({ agent: agentName, taskKey, ts: new Date().toISOString() }),
|
|
47
|
+
);
|
|
48
|
+
} catch { /* best effort */ }
|
|
49
|
+
|
|
50
|
+
process.exit(0);
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* lib/hooks/bootstrap-guard.mjs — enforce start-of-session bootstrap trio.
|
|
4
|
+
*
|
|
5
|
+
* Blocks Write/Edit/MultiEdit/NotebookEdit/TodoWrite and destructive Bash
|
|
6
|
+
* until the session has invoked workflow_status + project_context + memory_search
|
|
7
|
+
* (or accumulated 3+ read-style tool calls, the equivalent signal that the
|
|
8
|
+
* agent has grounded itself before acting).
|
|
9
|
+
*/
|
|
10
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'fs';
|
|
11
|
+
import { join } from 'path';
|
|
12
|
+
import { homedir } from 'os';
|
|
13
|
+
|
|
14
|
+
let payload = {};
|
|
15
|
+
try { payload = JSON.parse(readFileSync(0, 'utf8')) || {}; } catch { process.exit(0); }
|
|
16
|
+
|
|
17
|
+
const sessionId = payload.session_id || payload.sessionId || 'default';
|
|
18
|
+
const toolName = payload.tool_name || payload.tool || '';
|
|
19
|
+
const toolInput = payload.tool_input || payload.toolInput || {};
|
|
20
|
+
|
|
21
|
+
const BOOTSTRAP_TOOLS = new Set([
|
|
22
|
+
'mcp__construct-mcp__workflow_status',
|
|
23
|
+
'mcp__construct-mcp__project_context',
|
|
24
|
+
'mcp__construct-mcp__memory_search',
|
|
25
|
+
'mcp__cass__memory_search',
|
|
26
|
+
'workflow_status', 'project_context', 'memory_search',
|
|
27
|
+
]);
|
|
28
|
+
const READ_TOOLS = new Set(['Read', 'Grep', 'Glob', 'LS', 'NotebookRead']);
|
|
29
|
+
const BLOCKED_TOOLS = new Set(['Write', 'Edit', 'MultiEdit', 'NotebookEdit', 'TodoWrite']);
|
|
30
|
+
const COMPATIBLE_TOOLS = new Set(['Read', 'Grep', 'Glob', 'LS', 'NotebookRead']);
|
|
31
|
+
|
|
32
|
+
const BENIGN_BASH = /^(git (status|log|diff|branch|show|rev-parse)|ls|cat|head|tail|pwd|wc|file|echo|which|node --version|npm --version)\b/;
|
|
33
|
+
|
|
34
|
+
const stateDir = join(homedir(), '.cx');
|
|
35
|
+
const statePath = join(stateDir, 'bootstrap-state.json');
|
|
36
|
+
|
|
37
|
+
let state = {};
|
|
38
|
+
if (existsSync(statePath)) {
|
|
39
|
+
try { state = JSON.parse(readFileSync(statePath, 'utf8')) || {}; } catch { state = {}; }
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const now = Date.now();
|
|
43
|
+
for (const [k, v] of Object.entries(state)) {
|
|
44
|
+
if (!v?.ts || now - v.ts > 24 * 60 * 60 * 1000) delete state[k];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const s = state[sessionId] || { ts: now, reads: 0, bootstrap: new Set(), done: false };
|
|
48
|
+
if (Array.isArray(s.bootstrap)) s.bootstrap = new Set(s.bootstrap);
|
|
49
|
+
else if (!(s.bootstrap instanceof Set)) s.bootstrap = new Set();
|
|
50
|
+
s.ts = now;
|
|
51
|
+
|
|
52
|
+
if (BOOTSTRAP_TOOLS.has(toolName)) s.bootstrap.add(toolName.replace(/^.*__/, ''));
|
|
53
|
+
if (READ_TOOLS.has(toolName)) s.reads = (s.reads || 0) + 1;
|
|
54
|
+
|
|
55
|
+
if (!s.done) {
|
|
56
|
+
const hasTrio = s.bootstrap.has('workflow_status') && s.bootstrap.has('project_context') && s.bootstrap.has('memory_search');
|
|
57
|
+
if (hasTrio || (s.reads || 0) >= 3) s.done = true;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (!s.done && toolName && COMPATIBLE_TOOLS.has(toolName) && (s.reads || 0) >= 2) {
|
|
61
|
+
s.done = true;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
let block = false;
|
|
65
|
+
let reason = '';
|
|
66
|
+
if (!s.done) {
|
|
67
|
+
if (BLOCKED_TOOLS.has(toolName)) {
|
|
68
|
+
block = true;
|
|
69
|
+
reason = `${toolName} requires session bootstrap.`;
|
|
70
|
+
} else if (toolName === 'Bash') {
|
|
71
|
+
const cmd = (toolInput.command || '').trim();
|
|
72
|
+
if (cmd && !BENIGN_BASH.test(cmd)) { block = true; reason = 'Bash mutations require session bootstrap.'; }
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
mkdirSync(stateDir, { recursive: true });
|
|
78
|
+
state[sessionId] = { ...s, bootstrap: Array.from(s.bootstrap) };
|
|
79
|
+
writeFileSync(statePath, JSON.stringify(state));
|
|
80
|
+
} catch { /* best effort */ }
|
|
81
|
+
|
|
82
|
+
if (block) {
|
|
83
|
+
process.stderr.write(
|
|
84
|
+
`[bootstrap-guard] ${reason}\n` +
|
|
85
|
+
`Run in parallel first: workflow_status, project_context, memory_search.\n` +
|
|
86
|
+
`(Or do 3+ exploratory reads — Read/Grep/Glob/LS — if this is a pure exploration task.)\n`
|
|
87
|
+
);
|
|
88
|
+
process.exit(2);
|
|
89
|
+
}
|
|
90
|
+
process.exit(0);
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/hooks/comment-lint.mjs — PostToolUse hook: check edited files against comment policy.
|
|
3
|
+
*
|
|
4
|
+
* Runs after Write/Edit on scoped paths. Emits warnings for missing headers and
|
|
5
|
+
* banned patterns so violations are caught at authoring time, not at CI.
|
|
6
|
+
* Non-blocking: exits 0 even on warnings so it never halts a write.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
import { fileURLToPath } from 'node:url';
|
|
11
|
+
import { lintFile, formatResults } from '../comment-lint.mjs';
|
|
12
|
+
|
|
13
|
+
const filePath = process.env.TOOL_INPUT_FILE_PATH;
|
|
14
|
+
if (!filePath) process.exit(0);
|
|
15
|
+
|
|
16
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
17
|
+
if (!['.mjs', '.md', '.sh', '.js'].includes(ext)) process.exit(0);
|
|
18
|
+
|
|
19
|
+
const rootDir = path.resolve(fileURLToPath(import.meta.url), '..', '..', '..');
|
|
20
|
+
|
|
21
|
+
const result = lintFile(filePath, { rootDir });
|
|
22
|
+
if (!result.errors.length && !result.warnings.length) process.exit(0);
|
|
23
|
+
|
|
24
|
+
const { output } = formatResults([result]);
|
|
25
|
+
process.stdout.write(output);
|
|
26
|
+
process.exit(0);
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* lib/hooks/config-protection.mjs — Config protection hook — prevents edits to protected runtime config files.
|
|
4
|
+
*
|
|
5
|
+
* Runs as PreToolUse on Edit/Write. Blocks modifications to ~/.construct/config.env, settings.template.json, and other protected config paths. Exits 2 to block.
|
|
6
|
+
*/
|
|
7
|
+
import { readFileSync } from 'fs';
|
|
8
|
+
|
|
9
|
+
const filePath = process.env.TOOL_INPUT_FILE_PATH || (() => {
|
|
10
|
+
try { return JSON.parse(readFileSync(0, 'utf8'))?.tool_input?.file_path || ''; }
|
|
11
|
+
catch { return ''; }
|
|
12
|
+
})();
|
|
13
|
+
|
|
14
|
+
if (!filePath) process.exit(0);
|
|
15
|
+
|
|
16
|
+
const PROTECTED = [
|
|
17
|
+
/\.eslintrc(\.[a-z]+)?$/i,
|
|
18
|
+
/eslint\.config(\.[a-z]+)?$/i,
|
|
19
|
+
/\.prettierrc(\.[a-z]+)?$/i,
|
|
20
|
+
/prettier\.config(\.[a-z]+)?$/i,
|
|
21
|
+
/tsconfig(\.[^/]+)?\.json$/i,
|
|
22
|
+
/biome\.json$/i,
|
|
23
|
+
/\.stylelintrc(\.[a-z]+)?$/i,
|
|
24
|
+
/stylelint\.config(\.[a-z]+)?$/i,
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
const base = filePath.split('/').pop();
|
|
28
|
+
if (PROTECTED.some(r => r.test(base))) {
|
|
29
|
+
process.stderr.write(
|
|
30
|
+
`[config-protection] The code quality rules are protected. Fix the code to meet the existing standards — don't weaken the rules.\nFile: ${filePath}\n`
|
|
31
|
+
);
|
|
32
|
+
process.exit(2);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Meta-system protection: block edits to Construct's own critical files unless CX_ALLOW_META_EDIT=1
|
|
36
|
+
if (process.env.CX_ALLOW_META_EDIT !== '1') {
|
|
37
|
+
const META_FILES = [
|
|
38
|
+
/(?:^|\/)agents\/registry\.json$/,
|
|
39
|
+
/(?:^|\/)install\.sh$/,
|
|
40
|
+
/(?:^|\/)claude\/settings\.template\.json$/,
|
|
41
|
+
/(?:^|\/)lib\/hooks\/[^/]+\.mjs$/,
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
if (META_FILES.some(r => r.test(filePath))) {
|
|
45
|
+
process.stderr.write(
|
|
46
|
+
`[config-protection] This file is part of the Construct meta-system. Editing it affects all agent sessions and platforms. Set CX_ALLOW_META_EDIT=1 to proceed if you know what you're doing.\nFile: ${filePath}\n`
|
|
47
|
+
);
|
|
48
|
+
process.exit(2);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
process.exit(0);
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* lib/hooks/console-warn.mjs — Console warn hook — warns when console.log/debug statements are left in code.
|
|
4
|
+
*
|
|
5
|
+
* Runs as PostToolUse after Edit/Write. Detects console.log and console.debug statements in edited files and emits a warning. Does not block — informational only.
|
|
6
|
+
*/
|
|
7
|
+
import { readFileSync, appendFileSync, existsSync } from 'fs';
|
|
8
|
+
import { homedir } from 'os';
|
|
9
|
+
import { basename } from 'path';
|
|
10
|
+
|
|
11
|
+
const CODE_EXTS = new Set(['.js','.mjs','.cjs','.ts','.tsx','.jsx']);
|
|
12
|
+
const filePath = process.env.TOOL_INPUT_FILE_PATH || '';
|
|
13
|
+
|
|
14
|
+
if (!filePath) process.exit(0);
|
|
15
|
+
|
|
16
|
+
const ext = '.' + filePath.split('.').pop();
|
|
17
|
+
if (!CODE_EXTS.has(ext)) process.exit(0);
|
|
18
|
+
|
|
19
|
+
if (!existsSync(filePath)) process.exit(0);
|
|
20
|
+
|
|
21
|
+
const lines = readFileSync(filePath, 'utf8').split('\n');
|
|
22
|
+
const hits = [];
|
|
23
|
+
|
|
24
|
+
for (let i = 0; i < lines.length; i++) {
|
|
25
|
+
const line = lines[i];
|
|
26
|
+
const stripped = line.replace(/\/\/.*$/, '').replace(/\/\*.*?\*\//g, '');
|
|
27
|
+
if (/\bconsole\.(log|warn|error|info|debug)\s*\(/.test(stripped) || /\bdebugger\s*;/.test(stripped)) {
|
|
28
|
+
hits.push(i + 1);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (hits.length === 0) process.exit(0);
|
|
33
|
+
|
|
34
|
+
const file = basename(filePath);
|
|
35
|
+
const lineList = hits.slice(0, 5).join(', ') + (hits.length > 5 ? '…' : '');
|
|
36
|
+
const msg = `Heads up: debug statements left in ${file} — console.log on line${hits.length > 1 ? 's' : ''} ${lineList}. Remove before shipping.`;
|
|
37
|
+
|
|
38
|
+
process.stderr.write(`[console-warn] ${msg}\n`);
|
|
39
|
+
|
|
40
|
+
const flagsPath = `${homedir()}/.cx/warn-flags.txt`;
|
|
41
|
+
try { appendFileSync(flagsPath, `console.log in ${file} (lines ${lineList})\n`); } catch { /* best effort */ }
|
|
42
|
+
|
|
43
|
+
process.exit(0);
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* lib/hooks/context-window-recovery.mjs — Context window recovery hook — detects near-limit context and suggests compaction.
|
|
4
|
+
*
|
|
5
|
+
* Runs as PostToolUse. Reads context usage from the hook input and emits a compaction suggestion when the context window is above 80%. Non-blocking.
|
|
6
|
+
*/
|
|
7
|
+
import { readFileSync, writeFileSync, mkdirSync } from 'fs';
|
|
8
|
+
import { homedir } from 'os';
|
|
9
|
+
import { join } from 'path';
|
|
10
|
+
import { loadWorkflow, summarizeWorkflow } from '../workflow-state.mjs';
|
|
11
|
+
import { readContextState, writeContextState } from '../context-state.mjs';
|
|
12
|
+
|
|
13
|
+
const CONTEXT_LIMIT_PATTERNS = [
|
|
14
|
+
'context window', 'maximum context', 'too long', 'prompt is too long',
|
|
15
|
+
'context length exceeded', 'reduce the length', 'token limit', 'context limit',
|
|
16
|
+
'input is too long', 'exceeds the maximum',
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
const COOLDOWN_MS = 10 * 60 * 1000;
|
|
20
|
+
const STATE_PATH = join(homedir(), '.cx', 'context-recovery.json');
|
|
21
|
+
|
|
22
|
+
let input = {};
|
|
23
|
+
try { input = JSON.parse(readFileSync(0, 'utf8')); } catch { process.exit(0); }
|
|
24
|
+
|
|
25
|
+
const errorText = (
|
|
26
|
+
input?.error || input?.message ||
|
|
27
|
+
(input?.tool_response ? JSON.stringify(input.tool_response) : '') || ''
|
|
28
|
+
).toLowerCase();
|
|
29
|
+
|
|
30
|
+
const isContextLimit = CONTEXT_LIMIT_PATTERNS.some(p => errorText.includes(p));
|
|
31
|
+
if (!isContextLimit) process.exit(0);
|
|
32
|
+
|
|
33
|
+
const now = Date.now();
|
|
34
|
+
try {
|
|
35
|
+
const state = JSON.parse(readFileSync(STATE_PATH, 'utf8'));
|
|
36
|
+
if (now - (state.lastTriggeredAt || 0) < COOLDOWN_MS) process.exit(0);
|
|
37
|
+
} catch { /* first run */ }
|
|
38
|
+
|
|
39
|
+
const cxDir = join(homedir(), '.cx');
|
|
40
|
+
try { mkdirSync(cxDir, { recursive: true }); } catch { /* exists */ }
|
|
41
|
+
try { writeFileSync(STATE_PATH, JSON.stringify({ lastTriggeredAt: now })); } catch { /* best effort */ }
|
|
42
|
+
|
|
43
|
+
const cwd = input?.cwd || process.cwd();
|
|
44
|
+
|
|
45
|
+
const workflowLines = [];
|
|
46
|
+
try {
|
|
47
|
+
const workflow = loadWorkflow(cwd);
|
|
48
|
+
if (workflow) {
|
|
49
|
+
const summary = summarizeWorkflow(workflow);
|
|
50
|
+
workflowLines.push('## Active Workflow', summary, '');
|
|
51
|
+
const pending = (workflow.tasks || []).filter(t => t.status === 'in-progress' || t.status === 'todo');
|
|
52
|
+
if (pending.length) {
|
|
53
|
+
workflowLines.push('## Pending Tasks');
|
|
54
|
+
for (const t of pending.slice(0, 10)) {
|
|
55
|
+
workflowLines.push(`- [${t.key}] ${t.title} (${t.status}, owner: ${t.owner})`);
|
|
56
|
+
}
|
|
57
|
+
workflowLines.push('');
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
} catch { /* best effort */ }
|
|
61
|
+
|
|
62
|
+
let existingContext = '';
|
|
63
|
+
try {
|
|
64
|
+
const state = readContextState(cwd) || readContextState(homedir());
|
|
65
|
+
if (state?.markdown) existingContext = String(state.markdown).slice(0, 800);
|
|
66
|
+
else if (state?.context) existingContext = String(state.context).slice(0, 800);
|
|
67
|
+
} catch { /* none */ }
|
|
68
|
+
|
|
69
|
+
const date = new Date().toISOString().slice(0, 16).replace('T', ' ');
|
|
70
|
+
const recoveryContext = [
|
|
71
|
+
`# Session Context`,
|
|
72
|
+
`Last saved: ${date} (context-window-recovery)`,
|
|
73
|
+
'',
|
|
74
|
+
...workflowLines,
|
|
75
|
+
existingContext ? `## Prior Context\n${existingContext}` : '',
|
|
76
|
+
].join('\n') + '\n';
|
|
77
|
+
|
|
78
|
+
const projectCxDir = join(cwd, '.cx');
|
|
79
|
+
try { mkdirSync(projectCxDir, { recursive: true }); } catch { /* exists */ }
|
|
80
|
+
try { writeContextState(cwd, { source: 'context-window-recovery', recoveryContext, format: 'json' }, { markdown: recoveryContext }); } catch { /* best effort */ }
|
|
81
|
+
try { writeContextState(homedir(), { source: 'context-window-recovery', recoveryContext, format: 'json' }, { markdown: recoveryContext }); } catch { /* best effort */ }
|
|
82
|
+
|
|
83
|
+
process.stdout.write([
|
|
84
|
+
``,
|
|
85
|
+
`⚠ Context window limit hit. Session state saved to .cx/context.md.`,
|
|
86
|
+
`Resume by reading .cx/context.md and .cx/workflow.json, then continue from where you left off.`,
|
|
87
|
+
``,
|
|
88
|
+
].join('\n'));
|
|
89
|
+
|
|
90
|
+
process.exit(0);
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* lib/hooks/continuation-enforcer.mjs — Continuation enforcer hook — ensures agents complete tasks before stopping.
|
|
4
|
+
*
|
|
5
|
+
* Runs as PreToolUse on Stop. Checks workflow state for incomplete tasks and blocks the stop if high-priority work is unfinished, prompting the agent to continue.
|
|
6
|
+
*/
|
|
7
|
+
// PostToolUse(TodoWrite) — fires after every todo list update.
|
|
8
|
+
// 1. Counts remaining tasks and reminds agent to continue.
|
|
9
|
+
// 2. Updates .cx/drive-state.json with current loop iteration state.
|
|
10
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'fs';
|
|
11
|
+
import { join } from 'path';
|
|
12
|
+
|
|
13
|
+
let input = {};
|
|
14
|
+
try { input = JSON.parse(readFileSync(0, 'utf8')); } catch { process.exit(0); }
|
|
15
|
+
|
|
16
|
+
const cwd = input?.cwd || process.cwd();
|
|
17
|
+
const todos = input?.tool_input?.todos || [];
|
|
18
|
+
|
|
19
|
+
const pending = todos.filter(t => t.status === 'pending');
|
|
20
|
+
const inProgress = todos.filter(t => t.status === 'in_progress');
|
|
21
|
+
const done = todos.filter(t => t.status === 'completed');
|
|
22
|
+
const remaining = pending.length + inProgress.length;
|
|
23
|
+
|
|
24
|
+
if (remaining > 0) {
|
|
25
|
+
const parts = [`[continuation-enforcer] ${remaining} task${remaining !== 1 ? 's' : ''} remain. Continue until all are complete.`];
|
|
26
|
+
if (inProgress.length > 0) parts.push(` In progress: ${inProgress.map(t => t.content).join(', ')}`);
|
|
27
|
+
if (pending.length > 0) {
|
|
28
|
+
const shown = pending.slice(0, 5).map(t => t.content).join(', ');
|
|
29
|
+
const more = pending.length > 5 ? ` (+${pending.length - 5} more)` : '';
|
|
30
|
+
parts.push(` Pending: ${shown}${more}`);
|
|
31
|
+
}
|
|
32
|
+
process.stdout.write(parts.join('\n') + '\n');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Update .cx/drive-state.json if drive mode is active
|
|
36
|
+
const driveStatePath = join(cwd, '.cx', 'drive-state.json');
|
|
37
|
+
if (!existsSync(driveStatePath)) process.exit(0);
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
let ds = {};
|
|
41
|
+
try { ds = JSON.parse(readFileSync(driveStatePath, 'utf8')); } catch { /* fresh */ }
|
|
42
|
+
|
|
43
|
+
if (!ds.active) process.exit(0);
|
|
44
|
+
|
|
45
|
+
const total = todos.length;
|
|
46
|
+
ds.pendingTodos = remaining;
|
|
47
|
+
ds.updatedAt = new Date().toISOString();
|
|
48
|
+
ds.momentumScore = total > 0 ? done.length / total : 0;
|
|
49
|
+
ds.canStop = remaining === 0;
|
|
50
|
+
|
|
51
|
+
// Track current iteration snapshot
|
|
52
|
+
if (!ds.iterations) ds.iterations = [];
|
|
53
|
+
const n = ds.iteration || 1;
|
|
54
|
+
const existing = ds.iterations.findIndex(i => i.n === n);
|
|
55
|
+
const snapshot = {
|
|
56
|
+
n,
|
|
57
|
+
updatedAt: new Date().toISOString(),
|
|
58
|
+
pendingTodos: remaining,
|
|
59
|
+
doneTodos: done.length,
|
|
60
|
+
totalTodos: total,
|
|
61
|
+
inProgress: inProgress.map(t => t.content),
|
|
62
|
+
};
|
|
63
|
+
if (existing >= 0) ds.iterations[existing] = { ...ds.iterations[existing], ...snapshot };
|
|
64
|
+
else ds.iterations.push(snapshot);
|
|
65
|
+
|
|
66
|
+
// Increment iteration when all todos complete — ready for next loop pass
|
|
67
|
+
if (remaining === 0 && total > 0) ds.iteration = (ds.iteration || 1) + 1;
|
|
68
|
+
|
|
69
|
+
writeFileSync(driveStatePath, JSON.stringify(ds, null, 2));
|
|
70
|
+
} catch { /* best effort */ }
|
|
71
|
+
|
|
72
|
+
process.exit(0);
|