@geraldmaron/construct 1.0.4 → 1.0.6
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/README.md +57 -44
- package/agents/prompts/cx-accessibility.md +3 -3
- package/agents/prompts/cx-ai-engineer.md +7 -7
- package/agents/prompts/cx-architect.md +6 -6
- package/agents/prompts/cx-business-strategist.md +6 -6
- package/agents/prompts/cx-data-analyst.md +7 -7
- package/agents/prompts/cx-data-engineer.md +3 -3
- package/agents/prompts/cx-debugger.md +6 -6
- package/agents/prompts/cx-designer.md +5 -5
- package/agents/prompts/cx-devil-advocate.md +4 -4
- package/agents/prompts/cx-docs-keeper.md +5 -5
- package/agents/prompts/cx-engineer.md +5 -5
- package/agents/prompts/cx-evaluator.md +2 -2
- package/agents/prompts/cx-explorer.md +8 -8
- package/agents/prompts/cx-legal-compliance.md +2 -2
- package/agents/prompts/cx-operations.md +5 -5
- package/agents/prompts/cx-orchestrator.md +10 -10
- package/agents/prompts/cx-platform-engineer.md +5 -5
- package/agents/prompts/cx-product-manager.md +4 -4
- package/agents/prompts/cx-qa.md +8 -8
- package/agents/prompts/cx-rd-lead.md +9 -9
- package/agents/prompts/cx-release-manager.md +5 -5
- package/agents/prompts/cx-researcher.md +22 -22
- package/agents/prompts/cx-reviewer.md +7 -7
- package/agents/prompts/cx-security.md +10 -10
- package/agents/prompts/cx-sre.md +7 -7
- package/agents/prompts/cx-test-automation.md +3 -3
- package/agents/prompts/cx-trace-reviewer.md +8 -8
- package/agents/prompts/cx-ux-researcher.md +3 -3
- package/bin/construct +470 -4
- package/commands/build/feature.md +4 -4
- package/commands/build/fix.md +8 -8
- package/commands/design/access.md +3 -3
- package/commands/design/flow.md +3 -3
- package/commands/design/ui.md +4 -4
- package/commands/measure/experiment.md +5 -5
- package/commands/measure/metrics.md +3 -3
- package/commands/measure/results.md +4 -4
- package/commands/plan/api.md +3 -3
- package/commands/plan/challenge.md +3 -3
- package/commands/plan/decide.md +3 -3
- package/commands/plan/feature.md +7 -7
- package/commands/plan/requirements.md +3 -3
- package/commands/remember/context.md +5 -5
- package/commands/remember/handoff.md +3 -3
- package/commands/remember/runbook.md +3 -3
- package/commands/review/code.md +8 -8
- package/commands/review/quality.md +4 -4
- package/commands/review/security.md +3 -3
- package/commands/ship/ready.md +3 -3
- package/commands/ship/release.md +3 -3
- package/commands/ship/status.md +4 -4
- package/commands/understand/docs.md +3 -3
- package/commands/understand/research.md +5 -3
- package/commands/understand/this.md +4 -4
- package/commands/understand/why.md +8 -8
- package/commands/work/clean.md +14 -14
- package/commands/work/drive.md +10 -10
- package/commands/work/optimize-prompts.md +9 -9
- package/commands/work/parallel-review.md +8 -8
- package/db/schema/006_graph.sql +24 -0
- package/examples/provider-plugin/README.md +7 -7
- package/examples/seed-observations/README.md +6 -6
- package/examples/seed-observations/anti-patterns.md +14 -14
- package/examples/seed-observations/decisions.md +4 -4
- package/examples/seed-observations/patterns.md +14 -14
- package/lib/auto-docs.mjs +10 -5
- package/lib/cli-commands.mjs +45 -1
- package/lib/comment-lint.mjs +7 -1
- package/lib/config/schema.mjs +3 -0
- package/lib/flavors/loader.mjs +136 -0
- package/lib/hooks/agent-tracker.mjs +22 -3
- package/lib/hooks/pre-push-gate.mjs +14 -1
- package/lib/hooks/session-optimize.mjs +3 -2
- package/lib/hooks/session-reflect.mjs +68 -0
- package/lib/init-unified.mjs +25 -2
- package/lib/intake/classify.mjs +61 -183
- package/lib/intake/prepare.mjs +7 -0
- package/lib/intake/tables/creative.mjs +94 -0
- package/lib/intake/tables/operations.mjs +85 -0
- package/lib/intake/tables/research.mjs +85 -0
- package/lib/intake/tables/rnd.mjs +175 -0
- package/lib/knowledge/graph.mjs +213 -0
- package/lib/knowledge/research-store.mjs +109 -0
- package/lib/mcp/server.mjs +187 -1
- package/lib/mcp/tools/profile.mjs +270 -0
- package/lib/observation-store.mjs +19 -0
- package/lib/outcomes/aggregate.mjs +104 -0
- package/lib/outcomes/record.mjs +115 -0
- package/lib/parity.mjs +6 -9
- package/lib/profiles/lifecycle.mjs +388 -0
- package/lib/profiles/loader.mjs +123 -0
- package/lib/profiles/validate-custom.mjs +114 -0
- package/lib/reflect/extractor.mjs +193 -0
- package/lib/reflect.mjs +89 -2
- package/lib/sandbox.mjs +102 -0
- package/package.json +6 -1
- package/personas/construct.md +20 -20
- package/platforms/claude/CLAUDE.md +6 -6
- package/platforms/claude/settings.template.json +13 -0
- package/rules/common/agents.md +2 -2
- package/rules/common/beads-hygiene.md +11 -11
- package/rules/common/code-review.md +1 -1
- package/rules/common/coding-style.md +1 -1
- package/rules/common/comments.md +8 -8
- package/rules/common/commit-approval.md +4 -4
- package/rules/common/cx-agent-routing.md +2 -2
- package/rules/common/cx-skill-routing.md +2 -2
- package/rules/common/development-workflow.md +1 -1
- package/rules/common/doc-ownership.md +2 -2
- package/rules/common/efficiency.md +3 -3
- package/rules/common/framing.md +1 -1
- package/rules/common/git-workflow.md +1 -1
- package/rules/common/patterns.md +1 -1
- package/rules/common/performance.md +1 -1
- package/rules/common/release-gates.md +7 -7
- package/rules/common/research.md +4 -4
- package/rules/common/security.md +1 -1
- package/rules/common/skill-composition.md +8 -8
- package/rules/common/testing.md +1 -1
- package/rules/golang/coding-style.md +2 -2
- package/rules/golang/hooks.md +1 -1
- package/rules/golang/patterns.md +1 -1
- package/rules/golang/security.md +1 -1
- package/rules/golang/testing.md +1 -1
- package/rules/python/coding-style.md +1 -1
- package/rules/python/hooks.md +1 -1
- package/rules/python/patterns.md +1 -1
- package/rules/python/security.md +1 -1
- package/rules/python/testing.md +1 -1
- package/rules/swift/coding-style.md +3 -3
- package/rules/swift/hooks.md +2 -2
- package/rules/swift/patterns.md +2 -2
- package/rules/swift/security.md +4 -4
- package/rules/swift/testing.md +2 -2
- package/rules/typescript/coding-style.md +1 -1
- package/rules/typescript/hooks.md +1 -1
- package/rules/typescript/patterns.md +1 -1
- package/rules/typescript/security.md +1 -1
- package/rules/typescript/testing.md +1 -1
- package/rules/web/coding-style.md +1 -1
- package/rules/web/design-quality.md +1 -1
- package/rules/web/hooks.md +1 -1
- package/rules/web/patterns.md +1 -1
- package/rules/web/performance.md +1 -1
- package/rules/web/security.md +1 -1
- package/rules/web/testing.md +1 -1
- package/scripts/sync-agents.mjs +11 -0
- package/skills/ai/agent-dev.md +1 -1
- package/skills/ai/llm-security.md +1 -1
- package/skills/ai/ml-ops.md +6 -6
- package/skills/ai/orchestration-workflow.md +1 -1
- package/skills/ai/prompt-and-eval.md +1 -1
- package/skills/ai/prompt-optimizer.md +13 -13
- package/skills/ai/rag-system.md +1 -1
- package/skills/architecture/api-design.md +1 -1
- package/skills/architecture/caching.md +1 -1
- package/skills/architecture/cloud-native.md +1 -1
- package/skills/architecture/message-queue.md +1 -1
- package/skills/architecture/security-arch.md +1 -1
- package/skills/compliance/ai-disclosure.md +1 -1
- package/skills/compliance/data-privacy.md +1 -1
- package/skills/compliance/license-audit.md +2 -2
- package/skills/compliance/regulatory-review.md +1 -1
- package/skills/development/cpp.md +1 -1
- package/skills/development/go.md +1 -1
- package/skills/development/java.md +1 -1
- package/skills/development/kotlin.md +9 -9
- package/skills/development/mobile-crossplatform.md +13 -13
- package/skills/development/python.md +1 -1
- package/skills/development/rust.md +1 -1
- package/skills/development/shell.md +1 -1
- package/skills/development/swift.md +6 -6
- package/skills/development/typescript.md +1 -1
- package/skills/devops/ci-cd.md +5 -5
- package/skills/devops/containerization.md +9 -9
- package/skills/devops/cost-optimization.md +1 -1
- package/skills/devops/data-engineering.md +2 -2
- package/skills/devops/database.md +1 -1
- package/skills/devops/dependency-management.md +3 -3
- package/skills/devops/devsecops.md +1 -1
- package/skills/devops/git-workflow.md +1 -1
- package/skills/devops/incident-response.md +18 -18
- package/skills/devops/monorepo.md +5 -5
- package/skills/devops/observability.md +1 -1
- package/skills/devops/performance.md +1 -1
- package/skills/devops/testing.md +1 -1
- package/skills/docs/adr-workflow.md +2 -2
- package/skills/docs/backlog-proposal-workflow.md +1 -1
- package/skills/docs/customer-profile-workflow.md +1 -1
- package/skills/docs/document-ingest-workflow.md +1 -1
- package/skills/docs/evidence-ingest-workflow.md +1 -1
- package/skills/docs/init-docs.md +15 -15
- package/skills/docs/init-project.md +1 -1
- package/skills/docs/prd-workflow.md +3 -3
- package/skills/docs/prfaq-workflow.md +1 -1
- package/skills/docs/product-intelligence-review.md +1 -1
- package/skills/docs/product-intelligence-workflow.md +1 -1
- package/skills/docs/product-signal-workflow.md +9 -9
- package/skills/docs/research-workflow.md +10 -10
- package/skills/docs/runbook-workflow.md +2 -2
- package/skills/docs/strategy-workflow.md +3 -3
- package/skills/exploration/repo-map.md +11 -11
- package/skills/frameworks/django.md +15 -15
- package/skills/frameworks/nextjs.md +16 -16
- package/skills/frameworks/react.md +12 -12
- package/skills/frameworks/spring-boot.md +12 -12
- package/skills/frontend-design/accessibility.md +6 -6
- package/skills/frontend-design/component-patterns.md +1 -1
- package/skills/frontend-design/engineering.md +1 -1
- package/skills/frontend-design/state-management.md +1 -1
- package/skills/frontend-design/ui-aesthetics.md +1 -1
- package/skills/frontend-design/ux-principles.md +1 -1
- package/skills/operating/orchestration-reference.md +27 -27
- package/skills/quality-gates/review-work.md +3 -3
- package/skills/quality-gates/verify-change.md +1 -1
- package/skills/quality-gates/verify-module.md +1 -1
- package/skills/quality-gates/verify-quality.md +1 -1
- package/skills/quality-gates/verify-security.md +1 -1
- package/skills/roles/architect.ai-systems.md +4 -2
- package/skills/roles/architect.data.md +4 -2
- package/skills/roles/architect.enterprise.md +4 -2
- package/skills/roles/architect.integration.md +4 -2
- package/skills/roles/architect.md +7 -5
- package/skills/roles/architect.platform.md +4 -2
- package/skills/roles/data-analyst.experiment.md +4 -2
- package/skills/roles/data-analyst.md +9 -7
- package/skills/roles/data-analyst.product-intelligence.md +4 -2
- package/skills/roles/data-analyst.product.md +4 -2
- package/skills/roles/data-analyst.telemetry.md +4 -2
- package/skills/roles/data-engineer.pipeline.md +4 -2
- package/skills/roles/data-engineer.vector-retrieval.md +4 -2
- package/skills/roles/data-engineer.warehouse.md +4 -2
- package/skills/roles/debugger.md +7 -5
- package/skills/roles/designer.accessibility.md +4 -2
- package/skills/roles/designer.md +10 -8
- package/skills/roles/engineer.ai.md +4 -2
- package/skills/roles/engineer.data.md +5 -3
- package/skills/roles/engineer.md +14 -12
- package/skills/roles/engineer.platform.md +5 -3
- package/skills/roles/operator.docs.md +6 -4
- package/skills/roles/operator.md +6 -4
- package/skills/roles/operator.release.md +4 -2
- package/skills/roles/operator.sre.md +5 -3
- package/skills/roles/orchestrator.md +5 -3
- package/skills/roles/product-manager.ai-product.md +4 -2
- package/skills/roles/product-manager.business-strategy.md +4 -2
- package/skills/roles/product-manager.enterprise.md +4 -2
- package/skills/roles/product-manager.growth.md +4 -2
- package/skills/roles/product-manager.md +6 -4
- package/skills/roles/product-manager.platform.md +4 -2
- package/skills/roles/product-manager.product.md +4 -2
- package/skills/roles/qa.ai-eval.md +4 -2
- package/skills/roles/qa.api-contract.md +4 -2
- package/skills/roles/qa.data-pipeline.md +4 -2
- package/skills/roles/qa.md +7 -5
- package/skills/roles/qa.test-automation.md +5 -3
- package/skills/roles/qa.web-ui.md +4 -2
- package/skills/roles/researcher.explorer.md +4 -2
- package/skills/roles/researcher.md +11 -9
- package/skills/roles/researcher.ux.md +4 -2
- package/skills/roles/reviewer.devil-advocate.md +4 -2
- package/skills/roles/reviewer.evaluator.md +4 -2
- package/skills/roles/reviewer.md +14 -12
- package/skills/roles/reviewer.trace.md +4 -2
- package/skills/roles/security.ai.md +4 -2
- package/skills/roles/security.appsec.md +4 -2
- package/skills/roles/security.cloud.md +4 -2
- package/skills/roles/security.legal-compliance.md +4 -2
- package/skills/roles/security.md +7 -5
- package/skills/roles/security.privacy.md +4 -2
- package/skills/roles/security.supply-chain.md +4 -2
- package/skills/routing.md +14 -14
- package/skills/security/blue-team.md +1 -1
- package/skills/security/code-audit.md +1 -1
- package/skills/security/pentest.md +1 -1
- package/skills/security/red-team.md +1 -1
- package/skills/security/threat-intel.md +1 -1
- package/skills/security/vuln-research.md +1 -1
- package/skills/utility/clean-code.md +2 -2
- package/templates/docs/changelog-entry.md +1 -1
- package/templates/docs/construct_guide.md +13 -13
- package/templates/docs/meta-prd.md +16 -16
- package/templates/docs/one-pager.md +1 -1
- package/templates/docs/persona-artifact.md +36 -0
- package/templates/docs/prd-business.md +1 -1
- package/templates/docs/prd-platform.md +1 -1
- package/templates/docs/prd.md +17 -17
- package/templates/docs/research-brief.md +8 -8
- package/templates/docs/research-finding.md +26 -0
- package/templates/docs/rfc.md +1 -1
- package/templates/docs/skill-artifact.md +27 -0
- package/templates/docs/strategy.md +1 -1
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/profiles/validate-custom.mjs — Schema validator for user-defined profiles.
|
|
3
|
+
*
|
|
4
|
+
* Custom profiles live at `<project>/.cx/profile.json` with `custom: true`.
|
|
5
|
+
* They are validated on `construct sync` and via the pre-push gate. Rejected
|
|
6
|
+
* profiles never get loaded; resolveActiveProfile falls back to the default
|
|
7
|
+
* so a malformed escape-hatch file never breaks a project.
|
|
8
|
+
*
|
|
9
|
+
* Hard limits the validator enforces (kept in sync with schemas/profile.schema.json
|
|
10
|
+
* and scripts/lint-profiles.mjs):
|
|
11
|
+
* - max 12 stages
|
|
12
|
+
* - max 24 intake types
|
|
13
|
+
* - max 80 roles per profile
|
|
14
|
+
* - max 12 departments per profile
|
|
15
|
+
* - max 20 roles per department
|
|
16
|
+
* - classificationTable path must stay inside .cx/ (no repo escapes)
|
|
17
|
+
*
|
|
18
|
+
* Rationale for each cap lives in docs/concepts/persona-research.md.
|
|
19
|
+
*/
|
|
20
|
+
import fs from 'node:fs';
|
|
21
|
+
import path from 'node:path';
|
|
22
|
+
|
|
23
|
+
const MAX_STAGES = 12;
|
|
24
|
+
const MAX_INTAKE_TYPES = 24;
|
|
25
|
+
const MAX_ROLES = 80;
|
|
26
|
+
const MAX_DEPARTMENTS = 12;
|
|
27
|
+
const MAX_ROLES_PER_DEPARTMENT = 20;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @returns {string[]} array of error strings; empty means valid.
|
|
31
|
+
*/
|
|
32
|
+
export function validateCustomProfile(profile, { cwd } = {}) {
|
|
33
|
+
const errors = [];
|
|
34
|
+
if (!profile || typeof profile !== 'object') return ['profile is not an object'];
|
|
35
|
+
if (profile.custom !== true) errors.push('custom profiles must set custom: true');
|
|
36
|
+
|
|
37
|
+
if (!profile.id || !/^[a-z][a-z0-9-]{1,30}$/.test(profile.id)) {
|
|
38
|
+
errors.push('id must match ^[a-z][a-z0-9-]{1,30}$');
|
|
39
|
+
}
|
|
40
|
+
if (!profile.displayName || typeof profile.displayName !== 'string') {
|
|
41
|
+
errors.push('displayName is required');
|
|
42
|
+
}
|
|
43
|
+
if (!Array.isArray(profile.roles) || profile.roles.length === 0) {
|
|
44
|
+
errors.push('roles must be a non-empty array');
|
|
45
|
+
} else if (profile.roles.length > MAX_ROLES) {
|
|
46
|
+
errors.push(`roles exceeds max of ${MAX_ROLES}`);
|
|
47
|
+
}
|
|
48
|
+
if (!profile.intake || typeof profile.intake !== 'object') {
|
|
49
|
+
errors.push('intake is required');
|
|
50
|
+
} else {
|
|
51
|
+
if (!Array.isArray(profile.intake.types) || profile.intake.types.length === 0) {
|
|
52
|
+
errors.push('intake.types must be a non-empty array');
|
|
53
|
+
} else if (profile.intake.types.length > MAX_INTAKE_TYPES) {
|
|
54
|
+
errors.push(`intake.types exceeds max of ${MAX_INTAKE_TYPES}`);
|
|
55
|
+
}
|
|
56
|
+
if (!Array.isArray(profile.intake.stages) || profile.intake.stages.length === 0) {
|
|
57
|
+
errors.push('intake.stages must be a non-empty array');
|
|
58
|
+
} else if (profile.intake.stages.length > MAX_STAGES) {
|
|
59
|
+
errors.push(`intake.stages exceeds max of ${MAX_STAGES}`);
|
|
60
|
+
}
|
|
61
|
+
if (typeof profile.intake.classificationTable === 'string') {
|
|
62
|
+
const t = profile.intake.classificationTable;
|
|
63
|
+
if (path.isAbsolute(t)) {
|
|
64
|
+
errors.push('intake.classificationTable must be a relative path');
|
|
65
|
+
} else if (cwd && !t.startsWith('.cx/')) {
|
|
66
|
+
errors.push('intake.classificationTable must live under .cx/ for custom profiles');
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (profile.departments !== undefined) {
|
|
71
|
+
if (!Array.isArray(profile.departments)) {
|
|
72
|
+
errors.push('departments must be an array');
|
|
73
|
+
} else {
|
|
74
|
+
if (profile.departments.length > MAX_DEPARTMENTS) {
|
|
75
|
+
errors.push(`departments exceeds max of ${MAX_DEPARTMENTS}`);
|
|
76
|
+
}
|
|
77
|
+
for (const [i, dept] of profile.departments.entries()) {
|
|
78
|
+
if (!dept || typeof dept !== 'object') {
|
|
79
|
+
errors.push(`departments[${i}] must be an object`);
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
if (!dept.id || !/^[a-z][a-z0-9-]{1,40}$/.test(dept.id)) {
|
|
83
|
+
errors.push(`departments[${i}].id is missing or malformed`);
|
|
84
|
+
}
|
|
85
|
+
if (!dept.charter || dept.charter.length < 20) {
|
|
86
|
+
errors.push(`departments[${i}].charter must be at least 20 chars (mission statement, not a label)`);
|
|
87
|
+
}
|
|
88
|
+
if (!Array.isArray(dept.roles) || dept.roles.length === 0) {
|
|
89
|
+
errors.push(`departments[${i}].roles must be a non-empty array`);
|
|
90
|
+
} else if (dept.roles.length > MAX_ROLES_PER_DEPARTMENT) {
|
|
91
|
+
errors.push(`departments[${i}].roles exceeds max of ${MAX_ROLES_PER_DEPARTMENT}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return errors;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Read and validate `<cwd>/.cx/profile.json`. Returns:
|
|
101
|
+
* { status: 'absent' } if the file does not exist
|
|
102
|
+
* { status: 'invalid', errors: [...] } if validation failed
|
|
103
|
+
* { status: 'ok', profile } if the file is a valid custom profile
|
|
104
|
+
*/
|
|
105
|
+
export function validateCustomProfileFile(cwd) {
|
|
106
|
+
const p = path.join(cwd, '.cx', 'profile.json');
|
|
107
|
+
if (!fs.existsSync(p)) return { status: 'absent' };
|
|
108
|
+
let raw;
|
|
109
|
+
try { raw = JSON.parse(fs.readFileSync(p, 'utf8')); }
|
|
110
|
+
catch (err) { return { status: 'invalid', errors: [`malformed JSON: ${err.message}`] }; }
|
|
111
|
+
const errors = validateCustomProfile(raw, { cwd });
|
|
112
|
+
if (errors.length > 0) return { status: 'invalid', errors };
|
|
113
|
+
return { status: 'ok', profile: raw };
|
|
114
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/reflect/extractor.mjs — Deterministic transcript → observation extractor.
|
|
3
|
+
*
|
|
4
|
+
* Pure-function pass over a Claude Code transcript JSONL. Produces a compact
|
|
5
|
+
* structured summary suitable for `addObservation()` at session end. No LLM
|
|
6
|
+
* call — the transcript itself is the source of truth.
|
|
7
|
+
*
|
|
8
|
+
* Caller responsibility: read the transcript file and pass parsed lines.
|
|
9
|
+
* Output is capped at MAX_BYTES to honor the A1 hook's per-session budget.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
// Hard cap on the summary content so a runaway session can't blow up the
|
|
13
|
+
// observation store. 5 KB matches the budget set in the A1 plan.
|
|
14
|
+
const MAX_BYTES = 5 * 1024;
|
|
15
|
+
const MAX_TAGS = 10;
|
|
16
|
+
const MAX_TOOL_TYPES = 8;
|
|
17
|
+
const MAX_FILES = 12;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Extract a session-summary observation from a parsed transcript.
|
|
21
|
+
*
|
|
22
|
+
* @param {object} args
|
|
23
|
+
* @param {object[]} args.entries — parsed transcript JSONL lines (objects)
|
|
24
|
+
* @param {string} args.cwd — session working directory
|
|
25
|
+
* @param {string} [args.sessionId] — session identifier
|
|
26
|
+
* @param {number} [args.durationMs] — wall-clock session duration
|
|
27
|
+
* @returns {object|null} observation payload, or null if transcript is unusable
|
|
28
|
+
*/
|
|
29
|
+
export function extractSessionObservation({ entries, cwd, sessionId, durationMs }) {
|
|
30
|
+
if (!Array.isArray(entries) || entries.length === 0) return null;
|
|
31
|
+
|
|
32
|
+
const stats = collectStats(entries);
|
|
33
|
+
if (stats.assistantTurns === 0 && stats.userTurns === 0) return null;
|
|
34
|
+
|
|
35
|
+
const finalAssistant = stats.lastAssistantText;
|
|
36
|
+
const headline = deriveHeadline(finalAssistant, stats);
|
|
37
|
+
|
|
38
|
+
const lines = [];
|
|
39
|
+
lines.push(`Session: ${stats.userTurns} user turns, ${stats.assistantTurns} assistant turns`);
|
|
40
|
+
if (stats.toolCallCount > 0) {
|
|
41
|
+
const tools = Array.from(stats.toolTypes.entries())
|
|
42
|
+
.sort((a, b) => b[1] - a[1])
|
|
43
|
+
.slice(0, MAX_TOOL_TYPES)
|
|
44
|
+
.map(([name, n]) => `${name}×${n}`)
|
|
45
|
+
.join(', ');
|
|
46
|
+
lines.push(`Tools: ${stats.toolCallCount} calls (${tools})`);
|
|
47
|
+
}
|
|
48
|
+
if (stats.filesTouched.size > 0) {
|
|
49
|
+
const files = Array.from(stats.filesTouched).slice(0, MAX_FILES);
|
|
50
|
+
lines.push(`Files touched: ${files.join(', ')}${stats.filesTouched.size > MAX_FILES ? ', …' : ''}`);
|
|
51
|
+
}
|
|
52
|
+
if (durationMs && Number.isFinite(durationMs)) {
|
|
53
|
+
lines.push(`Duration: ${Math.round(durationMs / 1000)}s`);
|
|
54
|
+
}
|
|
55
|
+
if (finalAssistant) {
|
|
56
|
+
lines.push('', 'Final response excerpt:', clamp(finalAssistant, 800));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const content = clamp(lines.join('\n'), MAX_BYTES);
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
role: stats.lastAgent || 'construct',
|
|
63
|
+
category: 'session-summary',
|
|
64
|
+
summary: headline,
|
|
65
|
+
content,
|
|
66
|
+
tags: buildTags(stats),
|
|
67
|
+
confidence: 0.7,
|
|
68
|
+
source: 'auto-reflect',
|
|
69
|
+
extras: {
|
|
70
|
+
sessionId: sessionId || null,
|
|
71
|
+
cwd,
|
|
72
|
+
durationMs: durationMs ?? null,
|
|
73
|
+
userTurns: stats.userTurns,
|
|
74
|
+
assistantTurns: stats.assistantTurns,
|
|
75
|
+
toolCallCount: stats.toolCallCount,
|
|
76
|
+
filesTouched: Array.from(stats.filesTouched).slice(0, MAX_FILES),
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Walk the transcript once and gather everything we need in one pass. Keeps
|
|
82
|
+
// the hook well under its latency budget even on long sessions.
|
|
83
|
+
|
|
84
|
+
function collectStats(entries) {
|
|
85
|
+
const toolTypes = new Map();
|
|
86
|
+
const filesTouched = new Set();
|
|
87
|
+
let assistantTurns = 0;
|
|
88
|
+
let userTurns = 0;
|
|
89
|
+
let toolCallCount = 0;
|
|
90
|
+
let lastAssistantText = '';
|
|
91
|
+
let lastAgent = null;
|
|
92
|
+
|
|
93
|
+
for (const entry of entries) {
|
|
94
|
+
if (!entry || typeof entry !== 'object') continue;
|
|
95
|
+
const type = entry.type;
|
|
96
|
+
|
|
97
|
+
if (type === 'user') {
|
|
98
|
+
userTurns += 1;
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (type === 'assistant') {
|
|
103
|
+
assistantTurns += 1;
|
|
104
|
+
const text = extractAssistantText(entry);
|
|
105
|
+
if (text) lastAssistantText = text;
|
|
106
|
+
const calls = extractToolCalls(entry);
|
|
107
|
+
for (const call of calls) {
|
|
108
|
+
toolCallCount += 1;
|
|
109
|
+
toolTypes.set(call.name, (toolTypes.get(call.name) || 0) + 1);
|
|
110
|
+
for (const f of call.files) filesTouched.add(f);
|
|
111
|
+
}
|
|
112
|
+
const agent = extractAgentName(entry);
|
|
113
|
+
if (agent) lastAgent = agent;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return { assistantTurns, userTurns, toolCallCount, toolTypes, filesTouched, lastAssistantText, lastAgent };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function extractAssistantText(entry) {
|
|
121
|
+
const message = entry.message ?? entry;
|
|
122
|
+
const content = message?.content;
|
|
123
|
+
if (typeof content === 'string') return content.trim();
|
|
124
|
+
if (Array.isArray(content)) {
|
|
125
|
+
const text = content
|
|
126
|
+
.filter((p) => p?.type === 'text' && typeof p.text === 'string')
|
|
127
|
+
.map((p) => p.text)
|
|
128
|
+
.join('\n')
|
|
129
|
+
.trim();
|
|
130
|
+
return text;
|
|
131
|
+
}
|
|
132
|
+
return '';
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function extractToolCalls(entry) {
|
|
136
|
+
const message = entry.message ?? entry;
|
|
137
|
+
const content = message?.content;
|
|
138
|
+
if (!Array.isArray(content)) return [];
|
|
139
|
+
const calls = [];
|
|
140
|
+
for (const part of content) {
|
|
141
|
+
if (part?.type !== 'tool_use') continue;
|
|
142
|
+
const name = part.name || 'unknown';
|
|
143
|
+
const files = [];
|
|
144
|
+
const input = part.input || {};
|
|
145
|
+
if (typeof input.file_path === 'string') files.push(shortenPath(input.file_path));
|
|
146
|
+
if (typeof input.path === 'string') files.push(shortenPath(input.path));
|
|
147
|
+
calls.push({ name, files });
|
|
148
|
+
}
|
|
149
|
+
return calls;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function extractAgentName(entry) {
|
|
153
|
+
// Claude Code may stamp the agent name on subagent transcripts. Fall through
|
|
154
|
+
// to null if not present — caller defaults to 'construct'.
|
|
155
|
+
return entry?.agent || entry?.subagent_type || entry?.message?.agent || null;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function deriveHeadline(finalText, stats) {
|
|
159
|
+
// First non-empty line of the final assistant turn is usually a good lead.
|
|
160
|
+
// Fall back to a structural summary when nothing useful is available.
|
|
161
|
+
if (finalText) {
|
|
162
|
+
const firstLine = finalText.split('\n').map((l) => l.trim()).find((l) => l.length > 0);
|
|
163
|
+
if (firstLine) {
|
|
164
|
+
const clean = firstLine.replace(/^[#*\->]\s*/, '').replace(/[`*_]/g, '');
|
|
165
|
+
return `[session] ${clamp(clean, 200)}`;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return `[session] ${stats.assistantTurns} turn${stats.assistantTurns === 1 ? '' : 's'}, ${stats.toolCallCount} tool call${stats.toolCallCount === 1 ? '' : 's'}`;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function buildTags(stats) {
|
|
172
|
+
const tags = ['auto-reflect', 'session-summary'];
|
|
173
|
+
// Surface the most-used tool family so search can filter on "Bash-heavy
|
|
174
|
+
// session", etc. without parsing content.
|
|
175
|
+
const top = Array.from(stats.toolTypes.entries()).sort((a, b) => b[1] - a[1]).slice(0, 3);
|
|
176
|
+
for (const [name] of top) tags.push(`tool:${name.toLowerCase()}`);
|
|
177
|
+
if (stats.filesTouched.size > 0) tags.push('edits');
|
|
178
|
+
return tags.slice(0, MAX_TAGS);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function shortenPath(p) {
|
|
182
|
+
if (typeof p !== 'string') return '';
|
|
183
|
+
// Strip a repo-root prefix when present so observations stay portable.
|
|
184
|
+
const cwd = process.cwd();
|
|
185
|
+
if (p.startsWith(cwd + '/')) return p.slice(cwd.length + 1);
|
|
186
|
+
return p;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function clamp(str, max) {
|
|
190
|
+
if (!str) return '';
|
|
191
|
+
if (str.length <= max) return str;
|
|
192
|
+
return str.slice(0, max - 1) + '…';
|
|
193
|
+
}
|
package/lib/reflect.mjs
CHANGED
|
@@ -5,11 +5,13 @@
|
|
|
5
5
|
* knowledge base with actionable insights for continuous self-improvement.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
9
|
-
import { join, resolve } from 'node:path';
|
|
8
|
+
import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'node:fs';
|
|
9
|
+
import { join, resolve, basename } from 'node:path';
|
|
10
|
+
import { execSync } from 'node:child_process';
|
|
10
11
|
import { addObservation } from './observation-store.mjs';
|
|
11
12
|
import { loadConstructEnv } from './env-config.mjs';
|
|
12
13
|
import { KNOWLEDGE_ROOT, KNOWLEDGE_SUBDIRS, inferKnowledgeTarget } from './knowledge/layout.mjs';
|
|
14
|
+
import { extractSessionObservation } from './reflect/extractor.mjs';
|
|
13
15
|
|
|
14
16
|
const HOME = process.env.HOME || process.env.USERPROFILE;
|
|
15
17
|
const USER_ENV_PATH = join(HOME, '.construct', 'config.env');
|
|
@@ -125,6 +127,91 @@ export async function runReflectCli(args) {
|
|
|
125
127
|
console.log(` Use 'construct ingest --target=${target} <file>' to manually add similar feedback in the future`);
|
|
126
128
|
}
|
|
127
129
|
|
|
130
|
+
/**
|
|
131
|
+
* Auto-reflect from a Claude Code Stop hook payload.
|
|
132
|
+
*
|
|
133
|
+
* Reads the transcript JSONL, extracts a deterministic session summary, and
|
|
134
|
+
* persists it via the standard observation store. Contract: exit fast, fail
|
|
135
|
+
* silent, never throw — every error path returns null so the hook always
|
|
136
|
+
* exits 0.
|
|
137
|
+
*
|
|
138
|
+
* Returns the recorded observation id on success, null on no-op.
|
|
139
|
+
*
|
|
140
|
+
* @param {object} args
|
|
141
|
+
* @param {string} args.transcriptPath — path to the Stop hook's transcript_path
|
|
142
|
+
* @param {string} args.cwd — session cwd (the project root)
|
|
143
|
+
* @param {string} [args.sessionId]
|
|
144
|
+
* @param {number} [args.durationMs]
|
|
145
|
+
* @returns {Promise<string|null>}
|
|
146
|
+
*/
|
|
147
|
+
export async function runReflectAuto({ transcriptPath, cwd, sessionId, durationMs }) {
|
|
148
|
+
if (!transcriptPath || !cwd) return null;
|
|
149
|
+
if (!existsSync(transcriptPath)) return null;
|
|
150
|
+
|
|
151
|
+
const entries = parseTranscript(transcriptPath);
|
|
152
|
+
if (entries.length === 0) return null;
|
|
153
|
+
|
|
154
|
+
const observation = extractSessionObservation({ entries, cwd, sessionId, durationMs });
|
|
155
|
+
if (!observation) return null;
|
|
156
|
+
|
|
157
|
+
// Skip trivial sessions: no tool calls AND no substantive final text. Avoids
|
|
158
|
+
// polluting the observation store with one-line acknowledgements.
|
|
159
|
+
const trivial =
|
|
160
|
+
observation.extras?.toolCallCount === 0 &&
|
|
161
|
+
(observation.content?.length || 0) < 120;
|
|
162
|
+
if (trivial) return null;
|
|
163
|
+
|
|
164
|
+
const project = safeProject(cwd);
|
|
165
|
+
const gitSha = safeGitSha(cwd);
|
|
166
|
+
|
|
167
|
+
// Session-id stays in tags so the index can filter by it without parsing.
|
|
168
|
+
// Structured metadata goes into the new extras field, not into content.
|
|
169
|
+
const sessionTag = sessionId ? `session:${sessionId.slice(0, 24)}` : null;
|
|
170
|
+
const tags = [...observation.tags];
|
|
171
|
+
if (sessionTag) tags.push(sessionTag);
|
|
172
|
+
|
|
173
|
+
const result = await addObservation(cwd, {
|
|
174
|
+
role: observation.role,
|
|
175
|
+
category: observation.category,
|
|
176
|
+
summary: observation.summary,
|
|
177
|
+
content: observation.content,
|
|
178
|
+
tags,
|
|
179
|
+
project,
|
|
180
|
+
gitSha,
|
|
181
|
+
confidence: observation.confidence,
|
|
182
|
+
source: observation.source,
|
|
183
|
+
extras: observation.extras ?? null,
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
return result?.id ?? null;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function safeProject(cwd) {
|
|
190
|
+
try { return basename(cwd) || null; } catch { return null; }
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function safeGitSha(cwd) {
|
|
194
|
+
try {
|
|
195
|
+
return execSync('git rev-parse HEAD', { cwd, stdio: ['ignore', 'pipe', 'ignore'], timeout: 200 })
|
|
196
|
+
.toString().trim().slice(0, 40) || null;
|
|
197
|
+
} catch { return null; }
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Transcripts are JSONL; one JSON object per line. Be permissive — a single
|
|
201
|
+
// malformed line shouldn't lose the rest of the session.
|
|
202
|
+
|
|
203
|
+
function parseTranscript(path) {
|
|
204
|
+
let raw = '';
|
|
205
|
+
try { raw = readFileSync(path, 'utf8'); } catch { return []; }
|
|
206
|
+
const out = [];
|
|
207
|
+
for (const line of raw.split('\n')) {
|
|
208
|
+
const trimmed = line.trim();
|
|
209
|
+
if (!trimmed) continue;
|
|
210
|
+
try { out.push(JSON.parse(trimmed)); } catch { /* skip malformed */ }
|
|
211
|
+
}
|
|
212
|
+
return out;
|
|
213
|
+
}
|
|
214
|
+
|
|
128
215
|
/**
|
|
129
216
|
* CLI entry point for direct execution.
|
|
130
217
|
*/
|
package/lib/sandbox.mjs
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/sandbox.mjs — Lightweight isolated environment for QA validation.
|
|
3
|
+
*
|
|
4
|
+
* Spins up a fresh tmpdir with a minimal `.cx/` so a specialist (or operator)
|
|
5
|
+
* can run end-to-end checks against a candidate change without touching the
|
|
6
|
+
* working repo. Designed to be the cheapest possible alternative to a full
|
|
7
|
+
* Docker container: when Docker is unavailable, an isolated tmpdir is enough
|
|
8
|
+
* for almost every validation Construct needs.
|
|
9
|
+
*
|
|
10
|
+
* Layout created:
|
|
11
|
+
* <sandbox>/.cx/
|
|
12
|
+
* context.md stub
|
|
13
|
+
* observations/ empty
|
|
14
|
+
* intake/pending/ empty
|
|
15
|
+
* <sandbox>/.git/ empty repo init, so git-aware tools work
|
|
16
|
+
*
|
|
17
|
+
* Tear-down is opt-in via `construct sandbox prune` or by hand. Each sandbox
|
|
18
|
+
* lives under `~/.cx/sandboxes/<id>/` so they survive across CLI sessions.
|
|
19
|
+
*/
|
|
20
|
+
import fs from 'node:fs';
|
|
21
|
+
import os from 'node:os';
|
|
22
|
+
import path from 'node:path';
|
|
23
|
+
import { spawnSync } from 'node:child_process';
|
|
24
|
+
|
|
25
|
+
const ROOT = path.join(os.homedir(), '.cx', 'sandboxes');
|
|
26
|
+
|
|
27
|
+
function ensureRoot() {
|
|
28
|
+
fs.mkdirSync(ROOT, { recursive: true });
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function newSandboxId() {
|
|
32
|
+
const stamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
|
|
33
|
+
const rand = Math.random().toString(36).slice(2, 8);
|
|
34
|
+
return `${stamp}-${rand}`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Create a new sandbox. Returns its absolute path.
|
|
39
|
+
*
|
|
40
|
+
* @param {object} [opts]
|
|
41
|
+
* @param {string} [opts.profile] - profile id to write into construct.config.json
|
|
42
|
+
* @param {string} [opts.seedContext] - text to drop into .cx/context.md
|
|
43
|
+
* @returns {{ id: string, path: string }}
|
|
44
|
+
*/
|
|
45
|
+
export function createSandbox({ profile = null, seedContext = null } = {}) {
|
|
46
|
+
ensureRoot();
|
|
47
|
+
const id = newSandboxId();
|
|
48
|
+
const root = path.join(ROOT, id);
|
|
49
|
+
fs.mkdirSync(root, { recursive: true });
|
|
50
|
+
fs.mkdirSync(path.join(root, '.cx', 'observations'), { recursive: true });
|
|
51
|
+
fs.mkdirSync(path.join(root, '.cx', 'intake', 'pending'), { recursive: true });
|
|
52
|
+
|
|
53
|
+
const ctx = seedContext || `# Sandbox ${id}\n\nIsolated environment for QA validation. Created ${new Date().toISOString()}.\n`;
|
|
54
|
+
fs.writeFileSync(path.join(root, '.cx', 'context.md'), ctx);
|
|
55
|
+
|
|
56
|
+
const cfg = { version: 1, alias: 'Construct', deployment: { mode: 'solo' } };
|
|
57
|
+
if (profile) cfg.profile = profile;
|
|
58
|
+
fs.writeFileSync(path.join(root, 'construct.config.json'), JSON.stringify(cfg, null, 2) + '\n');
|
|
59
|
+
|
|
60
|
+
spawnSync('git', ['init', '--quiet'], { cwd: root, stdio: 'ignore' });
|
|
61
|
+
|
|
62
|
+
return { id, path: root };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* List existing sandboxes, newest first.
|
|
67
|
+
*/
|
|
68
|
+
export function listSandboxes() {
|
|
69
|
+
if (!fs.existsSync(ROOT)) return [];
|
|
70
|
+
const entries = fs.readdirSync(ROOT, { withFileTypes: true })
|
|
71
|
+
.filter((d) => d.isDirectory())
|
|
72
|
+
.map((d) => {
|
|
73
|
+
const full = path.join(ROOT, d.name);
|
|
74
|
+
const stat = fs.statSync(full);
|
|
75
|
+
return { id: d.name, path: full, createdAt: stat.birthtime.toISOString() };
|
|
76
|
+
});
|
|
77
|
+
return entries.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Delete a sandbox by id. Returns true if removed, false if not found.
|
|
82
|
+
*/
|
|
83
|
+
export function deleteSandbox(id) {
|
|
84
|
+
const full = path.join(ROOT, id);
|
|
85
|
+
if (!fs.existsSync(full)) return false;
|
|
86
|
+
fs.rmSync(full, { recursive: true, force: true });
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Prune sandboxes older than N days. Returns the count removed.
|
|
92
|
+
*/
|
|
93
|
+
export function pruneSandboxes({ olderThanDays = 7 } = {}) {
|
|
94
|
+
const cutoff = Date.now() - olderThanDays * 24 * 60 * 60 * 1000;
|
|
95
|
+
let removed = 0;
|
|
96
|
+
for (const s of listSandboxes()) {
|
|
97
|
+
if (Date.parse(s.createdAt) < cutoff) {
|
|
98
|
+
if (deleteSandbox(s.id)) removed++;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return removed;
|
|
102
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@geraldmaron/construct",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.6",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Construct — agent orchestration layer for OpenCode, Claude Code, and other coding surfaces",
|
|
6
6
|
"bin": {
|
|
@@ -42,12 +42,17 @@
|
|
|
42
42
|
"scripts": {
|
|
43
43
|
"mcp": "node lib/mcp/server.mjs",
|
|
44
44
|
"test": "node scripts/run-tests.mjs",
|
|
45
|
+
"test:functional": "node --test --test-timeout=30000 tests/functional/*.functional.test.mjs",
|
|
46
|
+
"test:unit": "node scripts/run-tests.mjs --exclude=tests/functional",
|
|
45
47
|
"docs:init": "node lib/init-docs.mjs --yes",
|
|
46
48
|
"docs:update": "node ./bin/construct docs:update",
|
|
47
49
|
"docs:site": "node ./bin/construct docs:site",
|
|
48
50
|
"docs:dev": "npm --prefix apps/docs run dev",
|
|
49
51
|
"docs:build": "npm --prefix apps/docs run build",
|
|
50
52
|
"lint:comments": "node ./bin/construct lint:comments",
|
|
53
|
+
"lint:prose": "node scripts/lint-prose.mjs",
|
|
54
|
+
"lint:profiles": "node scripts/lint-profiles.mjs",
|
|
55
|
+
"learning:status": "node scripts/learning-status.mjs",
|
|
51
56
|
"lint:templates": "node scripts/lint-commits-pr.mjs",
|
|
52
57
|
"eval:routing": "node scripts/eval/score-intent-classifier.mjs",
|
|
53
58
|
"release:check": "node ./bin/construct doctor && npm test && node ./bin/construct docs:update --check && node ./bin/construct dashboard:sync --check && node ./bin/construct lint:comments && node scripts/lint-commits-pr.mjs",
|
package/personas/construct.md
CHANGED
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
<!--
|
|
2
|
-
personas/construct.md
|
|
2
|
+
personas/construct.md. Construct persona prompt.
|
|
3
3
|
|
|
4
4
|
Defines the single user-facing AI interface and its session-start behavior,
|
|
5
5
|
routing rules, approval boundaries, and output contract. Loaded by sync-agents
|
|
6
6
|
and emitted to every supported platform.
|
|
7
7
|
-->
|
|
8
|
-
You are Construct
|
|
8
|
+
You are Construct. the single AI interface for everything from a quick question to a full software lifecycle. The user talks only to you; internal routing and specialist dispatch are implementation detail.
|
|
9
9
|
|
|
10
10
|
## Start of every session
|
|
11
11
|
|
|
12
|
-
Before responding, run in parallel
|
|
13
|
-
1. `project_context
|
|
14
|
-
2. `memory_search` with the basename of CWD
|
|
12
|
+
Before responding, run in parallel. do not narrate:
|
|
13
|
+
1. `project_context`. state from `.cx/context.md`
|
|
14
|
+
2. `memory_search` with the basename of CWD. prior session context and user preferences
|
|
15
15
|
3. Read `AGENTS.md`, `plan.md`, and the relevant docs for the current task when present
|
|
16
|
-
4. Check `.cx/handoffs/` for the most recent handoff
|
|
16
|
+
4. Check `.cx/handoffs/` for the most recent handoff. if another session was active, read it to understand what was in progress and what NOT to touch
|
|
17
17
|
|
|
18
18
|
Apply results silently. If memory returns preferences or past decisions, honor them without asking the user to repeat.
|
|
19
19
|
|
|
20
20
|
Honor the project operating hierarchy:
|
|
21
|
-
- Beads (`bd`) is the durable source of truth for tasks; hygiene contract
|
|
21
|
+
- Beads (`bd`) is the durable source of truth for tasks; hygiene contract. claim, close, supersede, prune. lives in `rules/common/beads-hygiene.md`. `bd ready` for unblocked work, `bd show <id>` for the active issue.
|
|
22
22
|
- `plan.md` is the human-readable implementation plan
|
|
23
23
|
- cass-memory via MCP `memory` is for cross-tool/session recall, not task tracking
|
|
24
24
|
|
|
@@ -26,12 +26,12 @@ Use the single-writer rule whenever multiple sessions are active: if two session
|
|
|
26
26
|
|
|
27
27
|
## Classify before acting
|
|
28
28
|
|
|
29
|
-
Use the code-backed orchestration policy for intent, execution track, specialist selection, escalation, and approval boundaries. Visual deliverables (wireframes, diagrams, decks) are first-class
|
|
29
|
+
Use the code-backed orchestration policy for intent, execution track, specialist selection, escalation, and approval boundaries. Visual deliverables (wireframes, diagrams, decks) are first-class. use real visual tools, not bullet prose.
|
|
30
30
|
|
|
31
31
|
Execution model:
|
|
32
|
-
- **Immediate
|
|
33
|
-
- **Focused
|
|
34
|
-
- **Orchestrated
|
|
32
|
+
- **Immediate**. answer or act directly when no hidden worker is needed.
|
|
33
|
+
- **Focused**. dispatch one bounded specialist path; return in Construct's voice.
|
|
34
|
+
- **Orchestrated**. plan → challenge → build → validate, with tracker-backed slices and explicit file ownership.
|
|
35
35
|
|
|
36
36
|
Devil's advocate is mandatory for new architectural directions, AI/agent workflow changes, security or data-integrity changes, and promoting a temporary capability to persistent.
|
|
37
37
|
|
|
@@ -39,9 +39,9 @@ Devil's advocate is mandatory for new architectural directions, AI/agent workflo
|
|
|
39
39
|
|
|
40
40
|
`routeRequest` returns three artifacts; honor all three:
|
|
41
41
|
|
|
42
|
-
1. **Gates
|
|
43
|
-
2. **Contract chain
|
|
44
|
-
3. **Specialist sequence
|
|
42
|
+
1. **Gates**. `framingChallenge`, `externalResearch`, `docAuthoring`
|
|
43
|
+
2. **Contract chain**. typed handoffs from `agents/contracts.json`. Call `agent_contract` MCP tool at handoff.
|
|
44
|
+
3. **Specialist sequence**. dispatch plan with ordering/parallel markers.
|
|
45
45
|
|
|
46
46
|
Before DONE: postconditions met · sources cited · framing logged · ADRs have Rejected alternatives.
|
|
47
47
|
|
|
@@ -50,9 +50,9 @@ Before DONE: postconditions met · sources cited · framing logged · ADRs have
|
|
|
50
50
|
- **Working branch is surfaced every session** at the top of session-start. Restate it before any mutating operation.
|
|
51
51
|
- **Never commit, push, or merge without asking first.** Before `git commit`, `git push`, or `gh pr merge`: state branch, show the proposed message / refspec / PR number verbatim, wait for explicit yes. A batch go-ahead covers a defined sequence; new commits later are their own gate. See `rules/common/commit-approval.md`.
|
|
52
52
|
|
|
53
|
-
##
|
|
53
|
+
## Intake surface
|
|
54
54
|
|
|
55
|
-
Session-start surfaces pending intake at `.cx/intake/pending/<id>.json`. Read with `construct intake show <id>`; the triage block names the primary owner, recommended chain, and next action. For non-trivial signals, plan with `construct graph from-intake <id>` and update node status with evidence (`construct graph status … done --evidence=…`). A node cannot reach `done` without an evidence record. Team / enterprise mode wraps tool calls in the MCP broker
|
|
55
|
+
The active profile (`construct profile show`) sets the intake taxonomy. Session-start surfaces pending intake at `.cx/intake/pending/<id>.json`. Read with `construct intake show <id>`; the triage block names the primary owner, recommended chain, and next action. For non-trivial signals, plan with `construct graph from-intake <id>` and update node status with evidence (`construct graph status … done --evidence=…`). A node cannot reach `done` without an evidence record. Team / enterprise mode wraps tool calls in the MCP broker; when it returns `ApprovalRequired`, surface the question and never bypass.
|
|
56
56
|
|
|
57
57
|
## Action discipline
|
|
58
58
|
|
|
@@ -73,15 +73,15 @@ Load-bearing state: `AGENTS.md`, `.cx/context.md`/`.json`, `docs/README.md`, `do
|
|
|
73
73
|
## Quality gates
|
|
74
74
|
|
|
75
75
|
After any implementation, dispatch validation before marking done:
|
|
76
|
-
1. cx-reviewer
|
|
77
|
-
2. cx-qa
|
|
76
|
+
1. cx-reviewer. correctness, regression, coverage
|
|
77
|
+
2. cx-qa. tests pass, coverage meets threshold
|
|
78
78
|
3. cx-security if auth/secrets/user data touched
|
|
79
79
|
|
|
80
80
|
Do not mark `done` until cx-reviewer and cx-qa return verdicts. BLOCKED or any CRITICAL finding stops shipping.
|
|
81
81
|
|
|
82
82
|
## Hard release gates
|
|
83
83
|
|
|
84
|
-
Run `npm run release:check` before any commit or push
|
|
84
|
+
Run `npm run release:check` before any commit or push. never wait for CI. Commits follow `.gitmessage`; PRs follow `.github/pull_request_template.md`. Full policy: `rules/common/release-gates.md`.
|
|
85
85
|
|
|
86
86
|
## Loop guard
|
|
87
87
|
|
|
@@ -91,6 +91,6 @@ Before stopping: surface incomplete tracker-linked plan slices and unmet accepta
|
|
|
91
91
|
|
|
92
92
|
## Drive mode
|
|
93
93
|
|
|
94
|
-
Activates on word-boundary triggers
|
|
94
|
+
Activates on word-boundary triggers. `/work:drive`, standalone `drive`, or `full send`. Substring matches do not count.
|
|
95
95
|
|
|
96
96
|
On trigger: orchestrated track, skip planning confirmation, continue until verification or a real blocker. State the dispatch plan upfront; brief status at phase transitions. User sees plan and outcomes, not deliberation.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Construct
|
|
1
|
+
# Construct: Claude Code Integration
|
|
2
2
|
|
|
3
3
|
This project uses Construct. Personas and specialists are defined in `agents/registry.json` and synced to Claude Code via `construct sync`.
|
|
4
4
|
|
|
@@ -6,11 +6,11 @@ This project uses Construct. Personas and specialists are defined in `agents/reg
|
|
|
6
6
|
|
|
7
7
|
Construct is the only intended user-facing surface.
|
|
8
8
|
|
|
9
|
-
- **Planning
|
|
10
|
-
- **Implementation
|
|
11
|
-
- **Validation
|
|
12
|
-
- **Research
|
|
13
|
-
- **Operations
|
|
9
|
+
- **Planning**: requirements, strategy, architecture
|
|
10
|
+
- **Implementation**: builds features and fixes bugs
|
|
11
|
+
- **Validation**: quality gates, security, accessibility
|
|
12
|
+
- **Research**: docs, debugging, codebase exploration
|
|
13
|
+
- **Operations**: releases, dev servers, health checks
|
|
14
14
|
|
|
15
15
|
## Usage
|
|
16
16
|
|