@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.
Files changed (268) hide show
  1. package/README.md +24 -18
  2. package/agents/contracts.json +617 -2
  3. package/agents/contracts.schema.json +83 -0
  4. package/agents/prompts/cx-accessibility.md +5 -3
  5. package/agents/prompts/cx-ai-engineer.md +9 -7
  6. package/agents/prompts/cx-architect.md +8 -6
  7. package/agents/prompts/cx-business-strategist.md +8 -6
  8. package/agents/prompts/cx-data-analyst.md +9 -7
  9. package/agents/prompts/cx-data-engineer.md +5 -3
  10. package/agents/prompts/cx-debugger.md +8 -6
  11. package/agents/prompts/cx-designer.md +7 -5
  12. package/agents/prompts/cx-devil-advocate.md +6 -4
  13. package/agents/prompts/cx-docs-keeper.md +7 -5
  14. package/agents/prompts/cx-engineer.md +7 -5
  15. package/agents/prompts/cx-evaluator.md +4 -2
  16. package/agents/prompts/cx-explorer.md +10 -8
  17. package/agents/prompts/cx-legal-compliance.md +4 -2
  18. package/agents/prompts/cx-operations.md +7 -5
  19. package/agents/prompts/cx-orchestrator.md +12 -10
  20. package/agents/prompts/cx-platform-engineer.md +7 -5
  21. package/agents/prompts/cx-product-manager.md +6 -4
  22. package/agents/prompts/cx-qa.md +10 -8
  23. package/agents/prompts/cx-rd-lead.md +11 -9
  24. package/agents/prompts/cx-release-manager.md +7 -5
  25. package/agents/prompts/cx-researcher.md +24 -22
  26. package/agents/prompts/cx-reviewer.md +9 -7
  27. package/agents/prompts/cx-security.md +12 -10
  28. package/agents/prompts/cx-sre.md +9 -7
  29. package/agents/prompts/cx-test-automation.md +5 -3
  30. package/agents/prompts/cx-trace-reviewer.md +10 -8
  31. package/agents/prompts/cx-ux-researcher.md +5 -3
  32. package/agents/registry.json +8 -3
  33. package/bin/construct +444 -53
  34. package/commands/build/feature.md +4 -4
  35. package/commands/build/fix.md +8 -8
  36. package/commands/design/access.md +3 -3
  37. package/commands/design/flow.md +3 -3
  38. package/commands/design/ui.md +4 -4
  39. package/commands/measure/experiment.md +5 -5
  40. package/commands/measure/metrics.md +3 -3
  41. package/commands/measure/results.md +4 -4
  42. package/commands/plan/api.md +3 -3
  43. package/commands/plan/challenge.md +3 -3
  44. package/commands/plan/decide.md +3 -3
  45. package/commands/plan/feature.md +7 -7
  46. package/commands/plan/requirements.md +3 -3
  47. package/commands/remember/context.md +5 -5
  48. package/commands/remember/handoff.md +3 -3
  49. package/commands/remember/runbook.md +3 -3
  50. package/commands/review/code.md +8 -8
  51. package/commands/review/quality.md +4 -4
  52. package/commands/review/security.md +3 -3
  53. package/commands/ship/ready.md +3 -3
  54. package/commands/ship/release.md +3 -3
  55. package/commands/ship/status.md +4 -4
  56. package/commands/understand/docs.md +3 -3
  57. package/commands/understand/this.md +4 -4
  58. package/commands/understand/why.md +8 -8
  59. package/commands/work/clean.md +14 -14
  60. package/commands/work/drive.md +10 -10
  61. package/commands/work/optimize-prompts.md +9 -9
  62. package/commands/work/parallel-review.md +8 -8
  63. package/db/schema/006_graph.sql +24 -0
  64. package/examples/provider-plugin/README.md +7 -7
  65. package/examples/seed-observations/README.md +6 -6
  66. package/examples/seed-observations/anti-patterns.md +14 -14
  67. package/examples/seed-observations/decisions.md +4 -4
  68. package/examples/seed-observations/patterns.md +14 -14
  69. package/lib/auto-docs.mjs +13 -8
  70. package/lib/boundary.mjs +126 -0
  71. package/lib/cache-strategy-google.js +26 -31
  72. package/lib/cli-commands.mjs +2 -2
  73. package/lib/comment-lint.mjs +134 -0
  74. package/lib/contracts/validate.mjs +323 -0
  75. package/lib/daemons/contract.mjs +210 -0
  76. package/lib/docs-verify.mjs +59 -6
  77. package/lib/doctor/cli.mjs +16 -1
  78. package/lib/doctor/index.mjs +3 -1
  79. package/lib/doctor/watchers/consistency.mjs +310 -0
  80. package/lib/doctor/watchers/mcp-protocol.mjs +232 -0
  81. package/lib/document-extract.mjs +211 -1
  82. package/lib/embed/cli.mjs +124 -3
  83. package/lib/embed/daemon.mjs +43 -4
  84. package/lib/embed/docs-lifecycle.mjs +1 -1
  85. package/lib/embed/inbox.mjs +2 -0
  86. package/lib/embed/scheduler.mjs +33 -5
  87. package/lib/evaluator-optimizer.mjs +2 -3
  88. package/lib/flavors/loader.mjs +1 -1
  89. package/lib/hooks/comment-lint.mjs +16 -0
  90. package/lib/hooks/mcp-audit.mjs +2 -1
  91. package/lib/hooks/proactive-activation.mjs +0 -14
  92. package/lib/hooks/rule-verifier.mjs +217 -0
  93. package/lib/hooks/session-optimize.mjs +2 -1
  94. package/lib/init-unified.mjs +55 -65
  95. package/lib/intake/classify.mjs +108 -24
  96. package/lib/intake/daemon.mjs +121 -0
  97. package/lib/intake/filesystem-queue.mjs +6 -1
  98. package/lib/intake/intake-config.mjs +2 -1
  99. package/lib/intake/prepare.mjs +0 -1
  100. package/lib/intake/session-prelude.mjs +7 -1
  101. package/lib/intake/traceability.mjs +90 -0
  102. package/lib/knowledge/graph.mjs +213 -0
  103. package/lib/knowledge/research-store.mjs +2 -0
  104. package/lib/maintenance/cleanup.mjs +315 -0
  105. package/lib/mcp/memory-bridge.mjs +276 -0
  106. package/lib/mcp/server.mjs +189 -1
  107. package/lib/mcp/tools/profile.mjs +270 -0
  108. package/lib/mcp/tools/workflow.mjs +25 -0
  109. package/lib/mcp-catalog.json +12 -8
  110. package/lib/mcp-platform-config.mjs +16 -8
  111. package/lib/migrations/index.mjs +106 -0
  112. package/lib/migrations/v1-baseline.mjs +33 -0
  113. package/lib/observation-store.mjs +9 -4
  114. package/lib/outcomes/record.mjs +2 -0
  115. package/lib/profiles/rebrand.mjs +46 -0
  116. package/lib/project-init-shared.mjs +12 -0
  117. package/lib/provider-capabilities.js +20 -7
  118. package/lib/providers/auth-manager.mjs +58 -17
  119. package/lib/reflect.mjs +49 -12
  120. package/lib/server/index.mjs +22 -28
  121. package/lib/session-store.mjs +6 -4
  122. package/lib/setup.mjs +14 -3
  123. package/lib/telemetry/client.mjs +5 -1
  124. package/lib/version.mjs +51 -0
  125. package/lib/worker/trace.mjs +5 -1
  126. package/package.json +4 -1
  127. package/personas/construct.md +3 -1
  128. package/platforms/claude/CLAUDE.md +6 -6
  129. package/rules/common/agents.md +2 -2
  130. package/rules/common/beads-hygiene.md +11 -11
  131. package/rules/common/code-review.md +1 -1
  132. package/rules/common/coding-style.md +1 -1
  133. package/rules/common/comments.md +8 -8
  134. package/rules/common/commit-approval.md +4 -4
  135. package/rules/common/cx-agent-routing.md +2 -2
  136. package/rules/common/cx-skill-routing.md +2 -2
  137. package/rules/common/development-workflow.md +3 -2
  138. package/rules/common/doc-ownership.md +2 -2
  139. package/rules/common/efficiency.md +3 -3
  140. package/rules/common/framing.md +1 -1
  141. package/rules/common/git-workflow.md +1 -1
  142. package/rules/common/no-fabrication.md +69 -0
  143. package/rules/common/patterns.md +1 -1
  144. package/rules/common/performance.md +1 -1
  145. package/rules/common/release-gates.md +7 -7
  146. package/rules/common/research.md +4 -4
  147. package/rules/common/review-before-change.md +58 -0
  148. package/rules/common/security.md +1 -1
  149. package/rules/common/skill-composition.md +8 -8
  150. package/rules/common/testing.md +1 -1
  151. package/rules/golang/coding-style.md +2 -2
  152. package/rules/golang/hooks.md +1 -1
  153. package/rules/golang/patterns.md +1 -1
  154. package/rules/golang/security.md +1 -1
  155. package/rules/golang/testing.md +1 -1
  156. package/rules/python/coding-style.md +1 -1
  157. package/rules/python/hooks.md +1 -1
  158. package/rules/python/patterns.md +1 -1
  159. package/rules/python/security.md +1 -1
  160. package/rules/python/testing.md +1 -1
  161. package/rules/swift/coding-style.md +3 -3
  162. package/rules/swift/hooks.md +2 -2
  163. package/rules/swift/patterns.md +2 -2
  164. package/rules/swift/security.md +4 -4
  165. package/rules/swift/testing.md +2 -2
  166. package/rules/typescript/coding-style.md +1 -1
  167. package/rules/typescript/hooks.md +1 -1
  168. package/rules/typescript/patterns.md +1 -1
  169. package/rules/typescript/security.md +1 -1
  170. package/rules/typescript/testing.md +1 -1
  171. package/rules/web/coding-style.md +1 -1
  172. package/rules/web/design-quality.md +1 -1
  173. package/rules/web/hooks.md +1 -1
  174. package/rules/web/patterns.md +1 -1
  175. package/rules/web/performance.md +1 -1
  176. package/rules/web/security.md +1 -1
  177. package/rules/web/testing.md +1 -1
  178. package/scripts/sync-agents.mjs +45 -14
  179. package/skills/ai/agent-dev.md +1 -1
  180. package/skills/ai/llm-security.md +1 -1
  181. package/skills/ai/ml-ops.md +6 -6
  182. package/skills/ai/orchestration-workflow.md +1 -1
  183. package/skills/ai/prompt-and-eval.md +1 -1
  184. package/skills/ai/prompt-optimizer.md +13 -13
  185. package/skills/ai/rag-system.md +1 -1
  186. package/skills/architecture/api-design.md +1 -1
  187. package/skills/architecture/caching.md +1 -1
  188. package/skills/architecture/cloud-native.md +1 -1
  189. package/skills/architecture/message-queue.md +1 -1
  190. package/skills/architecture/security-arch.md +1 -1
  191. package/skills/compliance/ai-disclosure.md +1 -1
  192. package/skills/compliance/data-privacy.md +1 -1
  193. package/skills/compliance/license-audit.md +2 -2
  194. package/skills/compliance/regulatory-review.md +1 -1
  195. package/skills/development/cpp.md +1 -1
  196. package/skills/development/go.md +1 -1
  197. package/skills/development/java.md +1 -1
  198. package/skills/development/kotlin.md +9 -9
  199. package/skills/development/mobile-crossplatform.md +13 -13
  200. package/skills/development/python.md +1 -1
  201. package/skills/development/rust.md +1 -1
  202. package/skills/development/shell.md +1 -1
  203. package/skills/development/swift.md +6 -6
  204. package/skills/development/typescript.md +1 -1
  205. package/skills/devops/ci-cd.md +5 -5
  206. package/skills/devops/containerization.md +9 -9
  207. package/skills/devops/cost-optimization.md +1 -1
  208. package/skills/devops/data-engineering.md +2 -2
  209. package/skills/devops/database.md +1 -1
  210. package/skills/devops/dependency-management.md +3 -3
  211. package/skills/devops/devsecops.md +1 -1
  212. package/skills/devops/git-workflow.md +1 -1
  213. package/skills/devops/incident-response.md +18 -18
  214. package/skills/devops/monorepo.md +5 -5
  215. package/skills/devops/observability.md +1 -1
  216. package/skills/devops/performance.md +1 -1
  217. package/skills/devops/testing.md +1 -1
  218. package/skills/docs/adr-workflow.md +2 -2
  219. package/skills/docs/backlog-proposal-workflow.md +1 -1
  220. package/skills/docs/customer-profile-workflow.md +1 -1
  221. package/skills/docs/document-ingest-workflow.md +1 -1
  222. package/skills/docs/evidence-ingest-workflow.md +1 -1
  223. package/skills/docs/init-docs.md +15 -15
  224. package/skills/docs/init-project.md +1 -1
  225. package/skills/docs/prd-workflow.md +3 -3
  226. package/skills/docs/prfaq-workflow.md +1 -1
  227. package/skills/docs/product-intelligence-review.md +1 -1
  228. package/skills/docs/product-intelligence-workflow.md +1 -1
  229. package/skills/docs/product-signal-workflow.md +9 -9
  230. package/skills/docs/research-workflow.md +10 -10
  231. package/skills/docs/runbook-workflow.md +2 -2
  232. package/skills/docs/strategy-workflow.md +3 -3
  233. package/skills/exploration/repo-map.md +11 -11
  234. package/skills/frameworks/django.md +15 -15
  235. package/skills/frameworks/nextjs.md +16 -16
  236. package/skills/frameworks/react.md +12 -12
  237. package/skills/frameworks/spring-boot.md +12 -12
  238. package/skills/frontend-design/accessibility.md +6 -6
  239. package/skills/frontend-design/component-patterns.md +1 -1
  240. package/skills/frontend-design/engineering.md +1 -1
  241. package/skills/frontend-design/state-management.md +1 -1
  242. package/skills/frontend-design/ui-aesthetics.md +1 -1
  243. package/skills/frontend-design/ux-principles.md +1 -1
  244. package/skills/operating/orchestration-reference.md +27 -27
  245. package/skills/quality-gates/review-work.md +3 -3
  246. package/skills/quality-gates/verify-change.md +1 -1
  247. package/skills/quality-gates/verify-module.md +1 -1
  248. package/skills/quality-gates/verify-quality.md +1 -1
  249. package/skills/quality-gates/verify-security.md +1 -1
  250. package/skills/routing.md +14 -14
  251. package/skills/security/blue-team.md +1 -1
  252. package/skills/security/code-audit.md +1 -1
  253. package/skills/security/pentest.md +1 -1
  254. package/skills/security/red-team.md +1 -1
  255. package/skills/security/threat-intel.md +1 -1
  256. package/skills/security/vuln-research.md +1 -1
  257. package/skills/utility/clean-code.md +2 -2
  258. package/templates/docs/changelog-entry.md +1 -1
  259. package/templates/docs/construct_guide.md +18 -18
  260. package/templates/docs/meta-prd.md +16 -16
  261. package/templates/docs/one-pager.md +1 -1
  262. package/templates/docs/prd-business.md +1 -1
  263. package/templates/docs/prd-platform.md +1 -1
  264. package/templates/docs/prd.md +17 -17
  265. package/templates/docs/research-brief.md +8 -8
  266. package/templates/docs/rfc.md +1 -1
  267. package/templates/docs/skill-artifact.md +1 -1
  268. 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 live capabilities (async).
121
- * Results are cached for 24h.
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 caps = await resolveProviderCapabilities(modelId);
133
+ const adapterKey = resolveAdapterKey(modelId);
134
+ const loader = ADAPTERS[adapterKey] || ADAPTERS.generic;
129
135
 
136
+ let caps;
130
137
  if (probe) {
131
- // TODO: implement live probing (API call) in Phase A follow-up
132
- // For now, just return the static capabilities
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
- // lib/providers/auth-manager.mjs
2
- // OAuth refresh and token lifecycle management
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
- const AUTH_DIR = path.join(homedir(), '.construct', 'auth');
9
-
10
- try {
11
- fs.mkdirSync(AUTH_DIR, { recursive: true, mode: 0o700 });
12
- } catch { /* ignore */ }
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(AUTH_DIR, `${provider}.json`);
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(AUTH_DIR, `${provider}.json`);
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
- // Provider-specific refresh logic would go here
121
- // For now, just indicate refresh is needed
122
- return {
123
- success: false,
124
- error: 'Token refresh required - reauthenticate manually',
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
- // If no summary provided, try to get it from stdin or prompt
51
+ let summarySource = 'cli';
51
52
  if (!summary) {
52
- // For now, we'll require explicit summary - in future could read from chat context
53
- console.error('Error: --summary=<text> is required to capture improvement feedback');
54
- console.error('Example: construct reflect --target=internal --summary="Improve Slack channel intent parsing to handle edge cases"');
55
- process.exit(1);
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
- // Record an observation about this feedback
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', // Feedback is typically an insight unless it's about a problem
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
  }
@@ -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
- // In a real implementation, this would:
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
- registeredAt: new Date().toISOString(),
1144
- boundaryVersion: '1.0'
1145
- };
1146
-
1147
- const boundaryConfigPath = join(configDir, 'boundary-config.json');
1148
- writeFileSync(boundaryConfigPath, JSON.stringify(boundaryConfig, null, 2));
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
+
@@ -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
- if (currentValue && currentValue !== desired) {
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 set to '.beads/hooks' to activate Construct policy gates.`,
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' });
@@ -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)) mkdirSync(dir, { recursive: true });
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(),
@@ -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
+ }
@@ -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)) mkdirSync(dir, { recursive: true });
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.5",
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",
@@ -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. 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.
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 Claude Code Integration
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** 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
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
 
@@ -1,5 +1,5 @@
1
1
  <!--
2
- rules/common/agents.md platform-agnostic agent orchestration guidance.
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 match the task to the right specialist:
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 Beads issue tracker hygiene contract.
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 Project Contract
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 human or AI, on any platform is responsible for keeping the tracker honest.
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 — verified: <file:line evidence>"`. Do not wait for someone else to notice. |
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` 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.
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 file them in the same session, not "later".
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 `construct intake` CLI" beats "intake CLI". |
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 auto-close on merge, pre-push `bd preflight` gate, weekly drift report, memory-contradiction detection. Until that ships, hygiene is a per-session discipline.
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 when and how to conduct code reviews.
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 language-agnostic coding standards.
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.