@geraldmaron/construct 1.0.5 → 1.0.7
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 +24 -18
- package/agents/contracts.json +617 -2
- package/agents/contracts.schema.json +83 -0
- package/agents/prompts/cx-accessibility.md +5 -3
- package/agents/prompts/cx-ai-engineer.md +9 -7
- package/agents/prompts/cx-architect.md +8 -6
- package/agents/prompts/cx-business-strategist.md +8 -6
- package/agents/prompts/cx-data-analyst.md +9 -7
- package/agents/prompts/cx-data-engineer.md +5 -3
- package/agents/prompts/cx-debugger.md +8 -6
- package/agents/prompts/cx-designer.md +7 -5
- package/agents/prompts/cx-devil-advocate.md +6 -4
- package/agents/prompts/cx-docs-keeper.md +7 -5
- package/agents/prompts/cx-engineer.md +7 -5
- package/agents/prompts/cx-evaluator.md +4 -2
- package/agents/prompts/cx-explorer.md +10 -8
- package/agents/prompts/cx-legal-compliance.md +4 -2
- package/agents/prompts/cx-operations.md +7 -5
- package/agents/prompts/cx-orchestrator.md +12 -10
- package/agents/prompts/cx-platform-engineer.md +7 -5
- package/agents/prompts/cx-product-manager.md +6 -4
- package/agents/prompts/cx-qa.md +10 -8
- package/agents/prompts/cx-rd-lead.md +11 -9
- package/agents/prompts/cx-release-manager.md +7 -5
- package/agents/prompts/cx-researcher.md +24 -22
- package/agents/prompts/cx-reviewer.md +9 -7
- package/agents/prompts/cx-security.md +12 -10
- package/agents/prompts/cx-sre.md +9 -7
- package/agents/prompts/cx-test-automation.md +5 -3
- package/agents/prompts/cx-trace-reviewer.md +10 -8
- package/agents/prompts/cx-ux-researcher.md +5 -3
- package/agents/registry.json +8 -3
- package/bin/construct +444 -53
- 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/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 +13 -8
- package/lib/boundary.mjs +126 -0
- package/lib/cache-strategy-google.js +26 -31
- package/lib/cli-commands.mjs +2 -2
- package/lib/comment-lint.mjs +134 -0
- package/lib/contracts/validate.mjs +323 -0
- package/lib/daemons/contract.mjs +210 -0
- package/lib/docs-verify.mjs +59 -6
- package/lib/doctor/cli.mjs +16 -1
- package/lib/doctor/index.mjs +3 -1
- package/lib/doctor/watchers/consistency.mjs +310 -0
- package/lib/doctor/watchers/mcp-protocol.mjs +232 -0
- package/lib/document-extract.mjs +211 -1
- package/lib/embed/cli.mjs +124 -3
- package/lib/embed/daemon.mjs +43 -4
- package/lib/embed/docs-lifecycle.mjs +1 -1
- package/lib/embed/inbox.mjs +2 -0
- package/lib/embed/scheduler.mjs +33 -5
- package/lib/evaluator-optimizer.mjs +2 -3
- package/lib/flavors/loader.mjs +1 -1
- package/lib/hooks/comment-lint.mjs +16 -0
- package/lib/hooks/mcp-audit.mjs +2 -1
- package/lib/hooks/proactive-activation.mjs +0 -14
- package/lib/hooks/rule-verifier.mjs +217 -0
- package/lib/hooks/session-optimize.mjs +2 -1
- package/lib/init-unified.mjs +55 -65
- package/lib/intake/classify.mjs +108 -24
- package/lib/intake/daemon.mjs +121 -0
- package/lib/intake/filesystem-queue.mjs +6 -1
- package/lib/intake/intake-config.mjs +2 -1
- package/lib/intake/prepare.mjs +0 -1
- package/lib/intake/session-prelude.mjs +7 -1
- package/lib/intake/traceability.mjs +90 -0
- package/lib/knowledge/graph.mjs +213 -0
- package/lib/knowledge/research-store.mjs +2 -0
- package/lib/maintenance/cleanup.mjs +315 -0
- package/lib/mcp/memory-bridge.mjs +276 -0
- package/lib/mcp/server.mjs +189 -1
- package/lib/mcp/tools/profile.mjs +270 -0
- package/lib/mcp/tools/workflow.mjs +25 -0
- package/lib/mcp-catalog.json +12 -8
- package/lib/mcp-platform-config.mjs +16 -8
- package/lib/migrations/index.mjs +106 -0
- package/lib/migrations/v1-baseline.mjs +33 -0
- package/lib/observation-store.mjs +9 -4
- package/lib/outcomes/record.mjs +2 -0
- package/lib/profiles/rebrand.mjs +46 -0
- package/lib/project-init-shared.mjs +12 -0
- package/lib/provider-capabilities.js +20 -7
- package/lib/providers/auth-manager.mjs +58 -17
- package/lib/reflect.mjs +49 -12
- package/lib/server/index.mjs +22 -28
- package/lib/session-store.mjs +6 -4
- package/lib/setup.mjs +14 -3
- package/lib/telemetry/client.mjs +5 -1
- package/lib/version.mjs +51 -0
- package/lib/worker/trace.mjs +5 -1
- package/package.json +4 -1
- package/personas/construct.md +3 -1
- package/platforms/claude/CLAUDE.md +6 -6
- 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 +3 -2
- 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/no-fabrication.md +69 -0
- 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/review-before-change.md +58 -0
- 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 +45 -14
- 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/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 +18 -18
- package/templates/docs/meta-prd.md +16 -16
- package/templates/docs/one-pager.md +1 -1
- 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/rfc.md +1 -1
- package/templates/docs/skill-artifact.md +1 -1
- package/templates/docs/strategy.md +1 -1
|
@@ -117,23 +117,36 @@ export function resolveProviderCapabilitiesSync(modelId) {
|
|
|
117
117
|
}
|
|
118
118
|
|
|
119
119
|
/**
|
|
120
|
-
* Probe provider for
|
|
121
|
-
*
|
|
120
|
+
* Probe provider for capabilities and cache the result for 24h.
|
|
121
|
+
*
|
|
122
|
+
* Static path (probe=false): resolves capabilities from the adapter's static
|
|
123
|
+
* declaration. Live path (probe=true): dispatches to the adapter's optional
|
|
124
|
+
* `probe(modelId)` export — adapters that don't implement it fall back to the
|
|
125
|
+
* static declaration. Construct itself stays provider-agnostic; per-vendor
|
|
126
|
+
* probing is the adapter's responsibility.
|
|
122
127
|
*
|
|
123
128
|
* @param {string} modelId
|
|
124
129
|
* @param {object} opts - { probe: boolean }
|
|
125
130
|
* @returns {Promise<object>}
|
|
126
131
|
*/
|
|
127
132
|
export async function probeProviderCapabilities(modelId, { probe = false } = {}) {
|
|
128
|
-
const
|
|
133
|
+
const adapterKey = resolveAdapterKey(modelId);
|
|
134
|
+
const loader = ADAPTERS[adapterKey] || ADAPTERS.generic;
|
|
129
135
|
|
|
136
|
+
let caps;
|
|
130
137
|
if (probe) {
|
|
131
|
-
|
|
132
|
-
|
|
138
|
+
try {
|
|
139
|
+
const mod = await loader();
|
|
140
|
+
caps = typeof mod.probe === 'function'
|
|
141
|
+
? await mod.probe(modelId)
|
|
142
|
+
: mod.capabilities(modelId);
|
|
143
|
+
} catch {
|
|
144
|
+
caps = await resolveProviderCapabilities(modelId);
|
|
145
|
+
}
|
|
146
|
+
} else {
|
|
147
|
+
caps = await resolveProviderCapabilities(modelId);
|
|
133
148
|
}
|
|
134
149
|
|
|
135
|
-
// Cache by adapter key
|
|
136
|
-
const adapterKey = resolveAdapterKey(modelId);
|
|
137
150
|
const cache = getCache();
|
|
138
151
|
cache[adapterKey] = caps;
|
|
139
152
|
writeCapabilityCache(cache);
|
|
@@ -1,15 +1,23 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* lib/providers/auth-manager.mjs — Token lifecycle and refresh dispatch.
|
|
3
|
+
*
|
|
4
|
+
* Provider-agnostic store and contract for credential rotation. The actual
|
|
5
|
+
* refresh flow (OAuth, JWT, vendor-specific) lives in a registered adapter;
|
|
6
|
+
* dispatch goes to the adapter's `refresh(state)` and the result is persisted
|
|
7
|
+
* back to the auth store. Adapters lacking a refresh implementation leave the
|
|
8
|
+
* operator on a reauthenticate-manually path — the safe agnostic default.
|
|
9
|
+
*/
|
|
3
10
|
|
|
4
11
|
import fs from 'node:fs';
|
|
5
12
|
import path from 'node:path';
|
|
6
13
|
import { homedir } from 'node:os';
|
|
7
14
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
fs.mkdirSync(
|
|
12
|
-
|
|
15
|
+
function authDir() {
|
|
16
|
+
const home = process.env.HOME || homedir();
|
|
17
|
+
const dir = path.join(home, '.construct', 'auth');
|
|
18
|
+
try { fs.mkdirSync(dir, { recursive: true, mode: 0o700 }); } catch { /* ignore */ }
|
|
19
|
+
return dir;
|
|
20
|
+
}
|
|
13
21
|
|
|
14
22
|
const PROVIDER_CONFIGS = {
|
|
15
23
|
github: {
|
|
@@ -28,9 +36,20 @@ const PROVIDER_CONFIGS = {
|
|
|
28
36
|
},
|
|
29
37
|
};
|
|
30
38
|
|
|
39
|
+
const refreshAdapters = new Map();
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Register a refresh adapter for a provider. Contract:
|
|
43
|
+
* adapter(state) -> Promise<{ success, token, refreshToken?, expiresAt? }>
|
|
44
|
+
* If no adapter is registered, refresh returns a manual-reauth instruction.
|
|
45
|
+
*/
|
|
46
|
+
export function registerRefreshAdapter(provider, adapter) {
|
|
47
|
+
if (typeof adapter === 'function') refreshAdapters.set(provider, adapter);
|
|
48
|
+
}
|
|
49
|
+
|
|
31
50
|
export function loadAuthState(provider) {
|
|
32
51
|
try {
|
|
33
|
-
const file = path.join(
|
|
52
|
+
const file = path.join(authDir(), `${provider}.json`);
|
|
34
53
|
if (fs.existsSync(file)) {
|
|
35
54
|
return JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
36
55
|
}
|
|
@@ -40,7 +59,7 @@ export function loadAuthState(provider) {
|
|
|
40
59
|
|
|
41
60
|
export function saveAuthState(provider, state) {
|
|
42
61
|
try {
|
|
43
|
-
const file = path.join(
|
|
62
|
+
const file = path.join(authDir(), `${provider}.json`);
|
|
44
63
|
fs.writeFileSync(file, JSON.stringify(state, null, 2), { mode: 0o600 });
|
|
45
64
|
return true;
|
|
46
65
|
} catch {
|
|
@@ -111,16 +130,38 @@ async function refreshToken(provider) {
|
|
|
111
130
|
if (!config?.refreshable) {
|
|
112
131
|
return { success: false, error: 'Provider does not support refresh' };
|
|
113
132
|
}
|
|
114
|
-
|
|
133
|
+
|
|
115
134
|
const state = loadAuthState(provider);
|
|
116
135
|
if (!state?.refreshToken) {
|
|
117
136
|
return { success: false, error: 'No refresh token available' };
|
|
118
137
|
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
138
|
+
|
|
139
|
+
const adapter = refreshAdapters.get(provider);
|
|
140
|
+
if (!adapter) {
|
|
141
|
+
return {
|
|
142
|
+
success: false,
|
|
143
|
+
error: 'Token refresh required - reauthenticate manually',
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
try {
|
|
148
|
+
const result = await adapter(state);
|
|
149
|
+
if (result?.success && result.token) {
|
|
150
|
+
const next = {
|
|
151
|
+
...state,
|
|
152
|
+
token: result.token,
|
|
153
|
+
refreshToken: result.refreshToken || state.refreshToken,
|
|
154
|
+
expiresAt: result.expiresAt || null,
|
|
155
|
+
rotatedAt: new Date().toISOString(),
|
|
156
|
+
};
|
|
157
|
+
saveAuthState(provider, next);
|
|
158
|
+
return { success: true, expiresAt: next.expiresAt };
|
|
159
|
+
}
|
|
160
|
+
return {
|
|
161
|
+
success: false,
|
|
162
|
+
error: result?.error || 'Refresh adapter returned no token',
|
|
163
|
+
};
|
|
164
|
+
} catch (err) {
|
|
165
|
+
return { success: false, error: `Refresh adapter threw: ${err.message}` };
|
|
166
|
+
}
|
|
126
167
|
}
|
package/lib/reflect.mjs
CHANGED
|
@@ -8,10 +8,11 @@
|
|
|
8
8
|
import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'node:fs';
|
|
9
9
|
import { join, resolve, basename } from 'node:path';
|
|
10
10
|
import { execSync } from 'node:child_process';
|
|
11
|
-
import { addObservation } from './observation-store.mjs';
|
|
11
|
+
import { addObservation, listObservations } from './observation-store.mjs';
|
|
12
12
|
import { loadConstructEnv } from './env-config.mjs';
|
|
13
13
|
import { KNOWLEDGE_ROOT, KNOWLEDGE_SUBDIRS, inferKnowledgeTarget } from './knowledge/layout.mjs';
|
|
14
14
|
import { extractSessionObservation } from './reflect/extractor.mjs';
|
|
15
|
+
import { readContextState, contextSummaryLine } from './context-state.mjs';
|
|
15
16
|
|
|
16
17
|
const HOME = process.env.HOME || process.env.USERPROFILE;
|
|
17
18
|
const USER_ENV_PATH = join(HOME, '.construct', 'config.env');
|
|
@@ -47,12 +48,19 @@ export async function runReflectCli(args) {
|
|
|
47
48
|
target = `knowledge/${target}`;
|
|
48
49
|
}
|
|
49
50
|
|
|
50
|
-
|
|
51
|
+
let summarySource = 'cli';
|
|
51
52
|
if (!summary) {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
53
|
+
const derived = deriveSummaryFromContext(process.cwd());
|
|
54
|
+
if (derived) {
|
|
55
|
+
summary = derived;
|
|
56
|
+
summarySource = 'auto-derived';
|
|
57
|
+
console.error(`Note: --summary not provided; using auto-derived summary from .cx/context.md and recent observations.`);
|
|
58
|
+
console.error(`Captured summary: "${summary.slice(0, 180)}${summary.length > 180 ? '…' : ''}"`);
|
|
59
|
+
} else {
|
|
60
|
+
console.error('Error: --summary=<text> is required (no .cx/context.md or recent session summaries to derive from)');
|
|
61
|
+
console.error('Example: construct reflect --target=internal --summary="Improve Slack channel intent parsing to handle edge cases"');
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
56
64
|
}
|
|
57
65
|
|
|
58
66
|
// Resolve the knowledge directory for this target
|
|
@@ -110,15 +118,14 @@ export async function runReflectCli(args) {
|
|
|
110
118
|
// Write the file
|
|
111
119
|
writeFileSync(filePath, content);
|
|
112
120
|
|
|
113
|
-
|
|
114
|
-
const observationSummary = `[reflect] Improvement feedback captured: ${summary.slice(0, 100)}${summary.length > 100 ? '...' : ''}`;
|
|
121
|
+
const observationSummary = `[reflect] Improvement feedback captured (${summarySource}): ${summary.slice(0, 100)}${summary.length > 100 ? '...' : ''}`;
|
|
115
122
|
addObservation(process.cwd(), {
|
|
116
123
|
role: 'construct',
|
|
117
|
-
category: 'insight',
|
|
124
|
+
category: 'insight',
|
|
118
125
|
summary: observationSummary,
|
|
119
|
-
content: `Feedback captured via construct reflect:\n\n${summary}\n\nStored as: ${filePath}\nTarget: ${knowledgeSubdir}/`,
|
|
120
|
-
tags: ['reflect', 'improvement-feedback', `knowledge:${knowledgeSubdir}`],
|
|
121
|
-
confidence: 0.9,
|
|
126
|
+
content: `Feedback captured via construct reflect (${summarySource}):\n\n${summary}\n\nStored as: ${filePath}\nTarget: ${knowledgeSubdir}/`,
|
|
127
|
+
tags: ['reflect', 'improvement-feedback', `knowledge:${knowledgeSubdir}`, `source:${summarySource}`],
|
|
128
|
+
confidence: summarySource === 'auto-derived' ? 0.65 : 0.9,
|
|
122
129
|
source: 'reflect-command',
|
|
123
130
|
});
|
|
124
131
|
|
|
@@ -186,6 +193,36 @@ export async function runReflectAuto({ transcriptPath, cwd, sessionId, durationM
|
|
|
186
193
|
return result?.id ?? null;
|
|
187
194
|
}
|
|
188
195
|
|
|
196
|
+
/**
|
|
197
|
+
* Derive a one-paragraph reflect summary from durable session state — the
|
|
198
|
+
* context.md digest first, then the most recent session-summary observation.
|
|
199
|
+
* Returns null when neither source yields useful content.
|
|
200
|
+
*/
|
|
201
|
+
export function deriveSummaryFromContext(cwd) {
|
|
202
|
+
const parts = [];
|
|
203
|
+
|
|
204
|
+
try {
|
|
205
|
+
const state = readContextState(cwd);
|
|
206
|
+
const line = contextSummaryLine(state);
|
|
207
|
+
if (line && line.trim().length > 20) parts.push(line.trim());
|
|
208
|
+
} catch { /* best effort */ }
|
|
209
|
+
|
|
210
|
+
try {
|
|
211
|
+
const recent = listObservations(cwd, { limit: 5 }) || [];
|
|
212
|
+
const sessionSummary = recent.find((o) => Array.isArray(o.tags) && o.tags.includes('session-summary'));
|
|
213
|
+
if (sessionSummary?.summary) {
|
|
214
|
+
const text = String(sessionSummary.summary).replace(/^\[[^\]]+\]\s*/, '').trim();
|
|
215
|
+
if (text.length > 20 && !parts.some((p) => p.includes(text.slice(0, 40)))) {
|
|
216
|
+
parts.push(text);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
} catch { /* best effort */ }
|
|
220
|
+
|
|
221
|
+
if (parts.length === 0) return null;
|
|
222
|
+
const joined = parts.join(' — ');
|
|
223
|
+
return joined.length > 600 ? joined.slice(0, 580) + '…' : joined;
|
|
224
|
+
}
|
|
225
|
+
|
|
189
226
|
function safeProject(cwd) {
|
|
190
227
|
try { return basename(cwd) || null; } catch { return null; }
|
|
191
228
|
}
|
package/lib/server/index.mjs
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* Runs on port 4242 (overridable via PORT env var), bound to 127.0.0.1.
|
|
9
9
|
*/
|
|
10
10
|
import { createServer } from 'http';
|
|
11
|
+
import { registerBoundary } from '../boundary.mjs';
|
|
11
12
|
import { readFileSync, writeFileSync, statSync, watch, existsSync, readdirSync, mkdirSync, renameSync, chmodSync, appendFileSync } from 'fs';
|
|
12
13
|
import { spawnSync } from 'child_process';
|
|
13
14
|
import { join, extname, relative, normalize, dirname } from 'path';
|
|
@@ -1115,44 +1116,36 @@ const server = createServer(async (req, res) => {
|
|
|
1115
1116
|
const chunks = [];
|
|
1116
1117
|
await new Promise((resolve, reject) => { req.on('data', c => chunks.push(c)); req.on('end', resolve); req.on('error', reject); });
|
|
1117
1118
|
const body = JSON.parse(Buffer.concat(chunks).toString('utf8') || '{}');
|
|
1118
|
-
|
|
1119
|
-
const { parentInstance, parentUrl, childInstanceId } = body;
|
|
1120
|
-
|
|
1119
|
+
|
|
1120
|
+
const { parentInstance, parentUrl, childInstanceId, nonce, signature } = body;
|
|
1121
|
+
|
|
1121
1122
|
if (!parentInstance || !parentUrl) {
|
|
1122
1123
|
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
1123
1124
|
res.end(JSON.stringify({ error: 'parentInstance and parentUrl are required' }));
|
|
1124
1125
|
return;
|
|
1125
1126
|
}
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
// - Validate the parent is a legitimate Construct instance
|
|
1129
|
-
// - Store the parent registration in a boundary configuration
|
|
1130
|
-
// - Set up communication channels between parent and child
|
|
1131
|
-
// - Configure isolation boundaries
|
|
1132
|
-
|
|
1133
|
-
// For now, just acknowledge the registration
|
|
1134
|
-
const configDir = join(HOME, '.construct');
|
|
1135
|
-
if (!existsSync(configDir)) {
|
|
1136
|
-
mkdirSync(configDir, { recursive: true });
|
|
1137
|
-
}
|
|
1138
|
-
|
|
1139
|
-
const boundaryConfig = {
|
|
1127
|
+
|
|
1128
|
+
const result = await registerBoundary({
|
|
1140
1129
|
parentInstance,
|
|
1141
1130
|
parentUrl,
|
|
1142
|
-
childInstanceId: childInstanceId || env.CONSTRUCT_INSTANCE_ID || 'default',
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1131
|
+
childInstanceId: childInstanceId || process.env.CONSTRUCT_INSTANCE_ID || 'default',
|
|
1132
|
+
nonce,
|
|
1133
|
+
signature,
|
|
1134
|
+
sharedSecret: process.env.CONSTRUCT_BOUNDARY_SECRET || null,
|
|
1135
|
+
});
|
|
1136
|
+
|
|
1137
|
+
if (!result.ok) {
|
|
1138
|
+
res.writeHead(result.status || 400, { 'Content-Type': 'application/json' });
|
|
1139
|
+
res.end(JSON.stringify({ error: result.error }));
|
|
1140
|
+
return;
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1150
1143
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1151
|
-
res.end(JSON.stringify({
|
|
1144
|
+
res.end(JSON.stringify({
|
|
1152
1145
|
success: true,
|
|
1153
1146
|
message: 'Boundary registration accepted',
|
|
1154
|
-
boundaryConfig,
|
|
1155
|
-
boundaryConfigPath
|
|
1147
|
+
boundaryConfig: result.config,
|
|
1148
|
+
boundaryConfigPath: result.path,
|
|
1156
1149
|
}));
|
|
1157
1150
|
} catch (err) {
|
|
1158
1151
|
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
@@ -2243,3 +2236,4 @@ process.on('SIGINT', () => shutdown('SIGINT'));
|
|
|
2243
2236
|
onEmbedNotification((event) => {
|
|
2244
2237
|
notifyClients({ type: 'toast', ...event });
|
|
2245
2238
|
});
|
|
2239
|
+
|
package/lib/session-store.mjs
CHANGED
|
@@ -19,6 +19,7 @@ import path from "node:path";
|
|
|
19
19
|
import { cosineSimilarity, rankByBm25 } from "./storage/embeddings.mjs";
|
|
20
20
|
import { embedSync as embedText } from "./storage/embeddings-legacy.mjs";
|
|
21
21
|
import { withFileLockSync } from "./storage/file-lock.mjs";
|
|
22
|
+
import { ensureCxDir } from "./project-init-shared.mjs";
|
|
22
23
|
|
|
23
24
|
const SESSIONS_DIR = ".cx/sessions";
|
|
24
25
|
const INDEX_FILE = "index.json";
|
|
@@ -29,7 +30,8 @@ const MAX_DECISIONS = 20;
|
|
|
29
30
|
const MAX_FILES = 50;
|
|
30
31
|
const MAX_OPEN_QUESTIONS = 10;
|
|
31
32
|
|
|
32
|
-
function ensureDir(dir) {
|
|
33
|
+
function ensureDir(dir, rootDir = null) {
|
|
34
|
+
if (rootDir) ensureCxDir(rootDir);
|
|
33
35
|
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
34
36
|
}
|
|
35
37
|
|
|
@@ -52,7 +54,7 @@ function readVectors(rootDir) {
|
|
|
52
54
|
}
|
|
53
55
|
|
|
54
56
|
function writeVectors(rootDir, records) {
|
|
55
|
-
ensureDir(sessionsDir(rootDir));
|
|
57
|
+
ensureDir(sessionsDir(rootDir), rootDir);
|
|
56
58
|
const target = vectorsPath(rootDir);
|
|
57
59
|
withFileLockSync(target, () => {
|
|
58
60
|
fs.writeFileSync(target, JSON.stringify(records.slice(0, MAX_INDEX_ENTRIES), null, 2) + "\n");
|
|
@@ -86,7 +88,7 @@ function readIndex(rootDir) {
|
|
|
86
88
|
}
|
|
87
89
|
|
|
88
90
|
function writeIndex(rootDir, entries) {
|
|
89
|
-
ensureDir(sessionsDir(rootDir));
|
|
91
|
+
ensureDir(sessionsDir(rootDir), rootDir);
|
|
90
92
|
const trimmed = entries.slice(0, MAX_INDEX_ENTRIES);
|
|
91
93
|
const target = indexPath(rootDir);
|
|
92
94
|
withFileLockSync(target, () => {
|
|
@@ -137,7 +139,7 @@ export function createSession(rootDir, {
|
|
|
137
139
|
taskSnapshot: [],
|
|
138
140
|
};
|
|
139
141
|
|
|
140
|
-
ensureDir(sessionsDir(rootDir));
|
|
142
|
+
ensureDir(sessionsDir(rootDir), rootDir);
|
|
141
143
|
fs.writeFileSync(
|
|
142
144
|
path.join(sessionsDir(rootDir), `${id}.json`),
|
|
143
145
|
JSON.stringify(record, null, 2) + "\n"
|
package/lib/setup.mjs
CHANGED
|
@@ -420,7 +420,7 @@ function ensureOpenCodeConfig() {
|
|
|
420
420
|
// repo but never executed by git. Idempotent: leaves a user-set value alone
|
|
421
421
|
// rather than clobbering it.
|
|
422
422
|
|
|
423
|
-
function ensureGitHooksPath({ cwd = process.cwd() } = {}) {
|
|
423
|
+
export function ensureGitHooksPath({ cwd = process.cwd() } = {}) {
|
|
424
424
|
const hooksDir = path.join(cwd, '.beads', 'hooks');
|
|
425
425
|
if (!fs.existsSync(path.join(hooksDir, 'pre-commit'))) {
|
|
426
426
|
return { status: 'skipped', reason: 'no .beads/hooks/pre-commit in this directory' };
|
|
@@ -435,10 +435,21 @@ function ensureGitHooksPath({ cwd = process.cwd() } = {}) {
|
|
|
435
435
|
if (currentValue === desired) {
|
|
436
436
|
return { status: 'ok', message: 'core.hooksPath already wired to .beads/hooks' };
|
|
437
437
|
}
|
|
438
|
-
|
|
438
|
+
|
|
439
|
+
// Treat the git default (`.git/hooks` or an absolute path to it) as
|
|
440
|
+
// equivalent to unset. A user who has accepted the default has not made an
|
|
441
|
+
// active choice that justifies "leave alone" semantics; without this branch
|
|
442
|
+
// an install that happens to land while `.git/hooks` is already in play
|
|
443
|
+
// leaves policy gates inactive permanently.
|
|
444
|
+
const isGitDefault =
|
|
445
|
+
!currentValue ||
|
|
446
|
+
currentValue === '.git/hooks' ||
|
|
447
|
+
currentValue === path.join(cwd, '.git', 'hooks') ||
|
|
448
|
+
currentValue.replace(/\/+$/, '').endsWith(`${path.sep}.git${path.sep}hooks`);
|
|
449
|
+
if (!isGitDefault) {
|
|
439
450
|
return {
|
|
440
451
|
status: 'warning',
|
|
441
|
-
message: `core.hooksPath is set to '${currentValue}'. Leaving alone
|
|
452
|
+
message: `core.hooksPath is set to '${currentValue}'. Leaving alone. Set to '.beads/hooks' to activate Construct policy gates.`,
|
|
442
453
|
};
|
|
443
454
|
}
|
|
444
455
|
const result = spawnSync('git', ['config', 'core.hooksPath', desired], { cwd, stdio: 'pipe', encoding: 'utf8' });
|
package/lib/telemetry/client.mjs
CHANGED
|
@@ -10,6 +10,7 @@ import { join } from 'node:path';
|
|
|
10
10
|
import { randomUUID } from 'node:crypto';
|
|
11
11
|
|
|
12
12
|
import { createIngestClient } from './ingest.mjs';
|
|
13
|
+
import { ensureCxDir } from '../project-init-shared.mjs';
|
|
13
14
|
|
|
14
15
|
const DEFAULT_MAX_BATCH = 50;
|
|
15
16
|
|
|
@@ -55,7 +56,10 @@ function appendLocalTelemetry(type, body, { rootDir, env = process.env, onError
|
|
|
55
56
|
const projectRoot = resolveProjectRoot({ rootDir, env });
|
|
56
57
|
const dir = join(projectRoot, '.cx', 'traces');
|
|
57
58
|
try {
|
|
58
|
-
if (!existsSync(dir))
|
|
59
|
+
if (!existsSync(dir)) {
|
|
60
|
+
ensureCxDir(projectRoot);
|
|
61
|
+
mkdirSync(dir, { recursive: true });
|
|
62
|
+
}
|
|
59
63
|
appendFileSync(join(dir, `${traceShard()}.jsonl`), `${JSON.stringify({
|
|
60
64
|
traceId: body?.traceId || body?.id || randomUUID(),
|
|
61
65
|
spanId: body?.id || randomUUID(),
|
package/lib/version.mjs
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/version.mjs — Single source of truth for the installed Construct version.
|
|
3
|
+
*
|
|
4
|
+
* Reads package.json once per process and caches. Surfaces:
|
|
5
|
+
* - bin/construct (`--version`, `construct version`)
|
|
6
|
+
* - construct status (header)
|
|
7
|
+
* - migration runner (compatibility check against artifact schema versions)
|
|
8
|
+
* - doctor watchers (advisory drift against published npm tag)
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { readFileSync } from 'node:fs';
|
|
12
|
+
import { dirname, join, resolve } from 'node:path';
|
|
13
|
+
import { fileURLToPath } from 'node:url';
|
|
14
|
+
|
|
15
|
+
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
16
|
+
const PKG_PATH = join(ROOT, 'package.json');
|
|
17
|
+
|
|
18
|
+
let _cached = null;
|
|
19
|
+
|
|
20
|
+
export function getInstalledVersion() {
|
|
21
|
+
if (_cached) return _cached;
|
|
22
|
+
try {
|
|
23
|
+
const pkg = JSON.parse(readFileSync(PKG_PATH, 'utf8'));
|
|
24
|
+
_cached = { version: pkg.version || '0.0.0', name: pkg.name || 'construct', pkgPath: PKG_PATH };
|
|
25
|
+
} catch {
|
|
26
|
+
_cached = { version: '0.0.0', name: 'construct', pkgPath: PKG_PATH };
|
|
27
|
+
}
|
|
28
|
+
return _cached;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Parse a semver string into { major, minor, patch }. Returns null on failure.
|
|
33
|
+
*/
|
|
34
|
+
export function parseSemver(s) {
|
|
35
|
+
const match = String(s || '').match(/^(\d+)\.(\d+)\.(\d+)/);
|
|
36
|
+
if (!match) return null;
|
|
37
|
+
return { major: Number(match[1]), minor: Number(match[2]), patch: Number(match[3]) };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Compare two semver strings. Returns -1 / 0 / 1.
|
|
42
|
+
*/
|
|
43
|
+
export function compareSemver(a, b) {
|
|
44
|
+
const pa = parseSemver(a);
|
|
45
|
+
const pb = parseSemver(b);
|
|
46
|
+
if (!pa || !pb) return 0;
|
|
47
|
+
if (pa.major !== pb.major) return pa.major < pb.major ? -1 : 1;
|
|
48
|
+
if (pa.minor !== pb.minor) return pa.minor < pb.minor ? -1 : 1;
|
|
49
|
+
if (pa.patch !== pb.patch) return pa.patch < pb.patch ? -1 : 1;
|
|
50
|
+
return 0;
|
|
51
|
+
}
|
package/lib/worker/trace.mjs
CHANGED
|
@@ -31,6 +31,7 @@ import { randomBytes } from 'node:crypto';
|
|
|
31
31
|
import path from 'node:path';
|
|
32
32
|
|
|
33
33
|
import { createTelemetryClient } from '../telemetry/client.mjs';
|
|
34
|
+
import { ensureCxDir } from '../project-init-shared.mjs';
|
|
34
35
|
|
|
35
36
|
const TRACE_SUBDIR = '.cx/traces';
|
|
36
37
|
|
|
@@ -150,7 +151,10 @@ export function emitTraceEvent({
|
|
|
150
151
|
}
|
|
151
152
|
|
|
152
153
|
const dir = traceDir(rootDir);
|
|
153
|
-
if (!existsSync(dir))
|
|
154
|
+
if (!existsSync(dir)) {
|
|
155
|
+
ensureCxDir(rootDir);
|
|
156
|
+
mkdirSync(dir, { recursive: true });
|
|
157
|
+
}
|
|
154
158
|
|
|
155
159
|
const event = {
|
|
156
160
|
traceId: traceId || newTraceId(),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@geraldmaron/construct",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.7",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Construct — agent orchestration layer for OpenCode, Claude Code, and other coding surfaces",
|
|
6
6
|
"bin": {
|
|
@@ -50,12 +50,15 @@
|
|
|
50
50
|
"docs:dev": "npm --prefix apps/docs run dev",
|
|
51
51
|
"docs:build": "npm --prefix apps/docs run build",
|
|
52
52
|
"lint:comments": "node ./bin/construct lint:comments",
|
|
53
|
+
"lint:contracts": "node ./bin/construct lint:contracts",
|
|
54
|
+
"lint:agents": "node ./bin/construct lint:agents",
|
|
53
55
|
"lint:prose": "node scripts/lint-prose.mjs",
|
|
54
56
|
"lint:profiles": "node scripts/lint-profiles.mjs",
|
|
55
57
|
"learning:status": "node scripts/learning-status.mjs",
|
|
56
58
|
"lint:templates": "node scripts/lint-commits-pr.mjs",
|
|
57
59
|
"eval:routing": "node scripts/eval/score-intent-classifier.mjs",
|
|
58
60
|
"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",
|
|
61
|
+
"release:gate": "node --test tests/functional/release-gate.functional.test.mjs",
|
|
59
62
|
"release:preflight": "node scripts/pre-release-check.mjs",
|
|
60
63
|
"release:preflight:no-auth": "node scripts/pre-release-check.mjs --skip-auth",
|
|
61
64
|
"npm:publish": "npm run release:check && npm publish --access public",
|
package/personas/construct.md
CHANGED
|
@@ -5,7 +5,9 @@ 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 user talks only to you; internal routing and specialist dispatch are implementation detail.
|
|
9
|
+
|
|
10
|
+
**Anti-fabrication contract**: every load-bearing claim cites a verifiable source. Missing source becomes `unknown` or `[unverified]`. Specialists tailor; the persona never weakens. See `rules/common/no-fabrication.md`.
|
|
9
11
|
|
|
10
12
|
## Start of every session
|
|
11
13
|
|
|
@@ -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
|
|
package/rules/common/agents.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<!--
|
|
2
|
-
rules/common/agents.md
|
|
2
|
+
rules/common/agents.md: platform-agnostic agent orchestration guidance.
|
|
3
3
|
|
|
4
4
|
Defines when to route to specialist agents, parallel execution rules,
|
|
5
5
|
and multi-perspective analysis patterns. Does not reference specific
|
|
@@ -9,7 +9,7 @@ platform agent types or config paths.
|
|
|
9
9
|
|
|
10
10
|
## Immediate Agent Usage
|
|
11
11
|
|
|
12
|
-
No user prompt needed
|
|
12
|
+
No user prompt needed: match the task to the right specialist:
|
|
13
13
|
1. Complex feature requests - planning specialist
|
|
14
14
|
2. Code just written/modified - code review specialist
|
|
15
15
|
3. Bug fix or new feature - TDD specialist
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<!--
|
|
2
|
-
rules/common/beads-hygiene.md
|
|
2
|
+
rules/common/beads-hygiene.md: Beads issue tracker hygiene contract.
|
|
3
3
|
|
|
4
4
|
Beads is the system of record for durable work in this project. Status drifts
|
|
5
5
|
when issues are not updated alongside the code, so every agent and persona
|
|
@@ -7,16 +7,16 @@ operating in Construct treats hygiene as part of the work, not as cleanup.
|
|
|
7
7
|
Loaded by AGENTS.md, CLAUDE.md, the construct persona, and the engineer /
|
|
8
8
|
operator / planner role overlays.
|
|
9
9
|
-->
|
|
10
|
-
# Beads Hygiene
|
|
10
|
+
# Beads Hygiene: Project Contract
|
|
11
11
|
|
|
12
|
-
Beads (`bd`) is the canonical durable tracker for Construct. Beads only earn their keep when their state matches the world. Stale "open" issues pollute `bd ready`, hide real work, and let agents propose work that already shipped. Every agent
|
|
12
|
+
Beads (`bd`) is the canonical durable tracker for Construct. Beads only earn their keep when their state matches the world. Stale "open" issues pollute `bd ready`, hide real work, and let agents propose work that already shipped. Every agent (human or AI, on any platform) is responsible for keeping the tracker honest.
|
|
13
13
|
|
|
14
14
|
## When to update beads
|
|
15
15
|
|
|
16
16
|
| Event | Required action |
|
|
17
17
|
|---|---|
|
|
18
18
|
| About to start non-trivial work | A Beads issue exists. `bd ready` to find or `bd create` if missing. `bd update <id> --claim` before edits. |
|
|
19
|
-
| Work lands on `main` | `bd close <id> --reason="Landed in PR #N
|
|
19
|
+
| Work lands on `main` | `bd close <id> --reason="Landed in PR #N. Verified: <file:line evidence>"`. Do not wait for someone else to notice. |
|
|
20
20
|
| Direction reverses mid-work | `bd supersede <old-id> --with=<new-id>`. Do not edit the old description in place. |
|
|
21
21
|
| Issue scope expands | Update the description and acceptance criteria in the same change that broadens scope. |
|
|
22
22
|
| A blocker is discovered | Add the dependency with `bd dep add <id> <depends-on>` so the readiness queue reflects reality. |
|
|
@@ -27,10 +27,10 @@ Beads (`bd`) is the canonical durable tracker for Construct. Beads only earn the
|
|
|
27
27
|
|
|
28
28
|
Run before planning, before claiming work, before proposing changes:
|
|
29
29
|
|
|
30
|
-
1. `bd ready
|
|
31
|
-
2. `bd list --status=in_progress
|
|
32
|
-
3. `bd stale
|
|
33
|
-
4. Cross-check the open list against `git log --oneline -20 origin/main
|
|
30
|
+
1. `bd ready`: surface unblocked work.
|
|
31
|
+
2. `bd list --status=in_progress`: verify nothing has been left mid-flight by an earlier session.
|
|
32
|
+
3. `bd stale`: surface anything untouched past the staleness window.
|
|
33
|
+
4. Cross-check the open list against `git log --oneline -20 origin/main`: close anything whose work actually landed.
|
|
34
34
|
|
|
35
35
|
If any of these surface drift, fix it before starting new work. Drift you observe and ignore becomes drift the next agent inherits.
|
|
36
36
|
|
|
@@ -40,14 +40,14 @@ After the code changes land in main:
|
|
|
40
40
|
|
|
41
41
|
1. The bead the work was claimed against is closed with evidence in the reason.
|
|
42
42
|
2. Any beads superseded by the change are marked superseded, not left open.
|
|
43
|
-
3. New beads exist for follow-up work that was discovered but not done
|
|
43
|
+
3. New beads exist for follow-up work that was discovered but not done: file them in the same session, not "later".
|
|
44
44
|
4. `bd doctor` and `bd preflight` should run before push and report clean.
|
|
45
45
|
|
|
46
46
|
## What goes in a bead
|
|
47
47
|
|
|
48
48
|
| Field | Standard |
|
|
49
49
|
|---|---|
|
|
50
|
-
| Title | Imperative, scoped, parseable. "PR 3
|
|
50
|
+
| Title | Imperative, scoped, parseable. "PR 3: `construct intake` CLI" beats "intake CLI". |
|
|
51
51
|
| Description | Why the bead exists + what success looks like. State the new shape directly. Do not preserve a "current behavior must keep working" goal unless the user explicitly asked for migration. |
|
|
52
52
|
| Acceptance criteria | Numbered, binary checks. A reviewer can answer pass/fail without re-reading the description. |
|
|
53
53
|
| Dependencies | Wire `bd dep add` whenever order matters. Implicit ordering rots into parallel work that breaks each other. |
|
|
@@ -72,4 +72,4 @@ There is no authorized bypass. Beads hygiene is a release gate. If the tooling i
|
|
|
72
72
|
|
|
73
73
|
## Automation
|
|
74
74
|
|
|
75
|
-
Project-level automation is tracked in the beads queue
|
|
75
|
+
Project-level automation is tracked in the beads queue: auto-close on merge, pre-push `bd preflight` gate, weekly drift report, memory-contradiction detection. Until that ships, hygiene is a per-session discipline.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<!--
|
|
2
|
-
rules/common/code-review.md
|
|
2
|
+
rules/common/code-review.md: when and how to conduct code reviews.
|
|
3
3
|
|
|
4
4
|
Defines mandatory review triggers, severity levels, approval criteria,
|
|
5
5
|
and references coding-style.md and security.md for checklists.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<!--
|
|
2
|
-
rules/common/coding-style.md
|
|
2
|
+
rules/common/coding-style.md: language-agnostic coding standards.
|
|
3
3
|
|
|
4
4
|
Covers immutability, core principles (KISS/DRY/YAGNI), file organization,
|
|
5
5
|
error handling, input validation, naming conventions, and quality checklist.
|