@geraldmaron/construct 1.0.14 → 1.0.16
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 +21 -2
- package/bin/construct +146 -83
- package/commands/build/feature.md +0 -6
- package/commands/build/fix.md +0 -6
- package/commands/design/access.md +0 -6
- package/commands/design/flow.md +0 -6
- package/commands/design/ui.md +0 -6
- package/commands/measure/experiment.md +0 -6
- package/commands/measure/metrics.md +0 -6
- package/commands/measure/results.md +0 -6
- package/commands/plan/api.md +0 -6
- package/commands/plan/challenge.md +0 -6
- package/commands/plan/decide.md +0 -6
- package/commands/plan/feature.md +0 -7
- package/commands/plan/requirements.md +0 -6
- package/commands/remember/context.md +0 -6
- package/commands/remember/handoff.md +0 -6
- package/commands/remember/runbook.md +0 -6
- package/commands/review/code.md +0 -6
- package/commands/review/quality.md +0 -6
- package/commands/review/security.md +0 -6
- package/commands/ship/ready.md +0 -6
- package/commands/ship/release.md +0 -6
- package/commands/ship/status.md +0 -6
- package/commands/understand/docs.md +0 -6
- package/commands/understand/research.md +0 -6
- package/commands/understand/this.md +0 -6
- package/commands/understand/why.md +0 -6
- package/commands/work/clean.md +0 -6
- package/commands/work/drive.md +0 -6
- package/commands/work/optimize-prompts.md +0 -6
- package/commands/work/parallel-review.md +0 -6
- package/db/schema/010_cx_scores.sql +51 -0
- package/lib/beads-client.mjs +19 -1
- package/lib/cli-commands.mjs +280 -146
- package/lib/comment-lint.mjs +5 -1
- package/lib/context-state.mjs +15 -3
- package/lib/contracts/validate.mjs +72 -18
- package/lib/contracts/violation-log.mjs +169 -0
- package/lib/document-extract/docling-client.mjs +114 -0
- package/lib/document-extract/docling-sidecar.py +122 -0
- package/lib/document-extract/whisper-client.mjs +79 -0
- package/lib/document-extract.mjs +73 -8
- package/lib/document-ingest.mjs +26 -4
- package/lib/embed/daemon.mjs +6 -1
- package/lib/flavors/loader.mjs +1 -1
- package/lib/handoffs/contract.mjs +2 -2
- package/lib/hooks/session-start.mjs +1 -1
- package/lib/hooks/stop-notify.mjs +7 -0
- package/lib/ingest/chunker.mjs +94 -0
- package/lib/ingest/pipeline.mjs +53 -0
- package/lib/ingest/store.mjs +82 -0
- package/lib/install/first-invocation.mjs +5 -1
- package/lib/intake/prepare.mjs +22 -4
- package/lib/integrations/intake-integrations.mjs +28 -2
- package/lib/knowledge/search.mjs +12 -0
- package/lib/mcp/server.mjs +142 -1
- package/lib/mcp/tools/skills.mjs +6 -3
- package/lib/mcp/tools/telemetry.mjs +30 -0
- package/lib/mcp/tools/workflow.mjs +6 -2
- package/lib/observation-store.mjs +14 -5
- package/lib/ollama-manager.mjs +32 -28
- package/lib/orchestration-policy.mjs +15 -0
- package/lib/prompt-composer.js +11 -1
- package/lib/roles/gateway.mjs +30 -1
- package/lib/runtime/uv-bootstrap.mjs +129 -0
- package/lib/runtime/whisper-bootstrap.mjs +102 -0
- package/lib/scheduler/index.mjs +24 -4
- package/lib/server/index.mjs +22 -1
- package/lib/server/insights.mjs +12 -0
- package/lib/server/static/index.html +1 -1
- package/lib/setup.mjs +54 -9
- package/lib/specialists/postconditions.mjs +1 -1
- package/lib/sync/skill-frontmatter.mjs +113 -21
- package/lib/tracking-surfaces.mjs +2 -0
- package/lib/update.mjs +31 -3
- package/lib/upgrade.mjs +31 -4
- package/lib/validators/skills.mjs +86 -54
- package/package.json +5 -5
- package/personas/construct.md +6 -7
- package/platforms/claude/CLAUDE.md +43 -15
- package/platforms/claude/settings.template.json +1 -1
- package/rules/common/beads-hygiene.md +3 -9
- package/rules/common/code-review.md +3 -6
- package/rules/common/coding-style.md +3 -6
- package/rules/common/comments.md +3 -7
- package/rules/common/commit-approval.md +3 -6
- package/rules/common/cx-agent-routing.md +3 -7
- package/rules/common/cx-skill-routing.md +3 -3
- package/rules/common/doc-ownership.md +3 -8
- package/rules/common/efficiency.md +3 -8
- package/rules/common/framing.md +3 -8
- package/rules/common/git-workflow.md +3 -5
- package/rules/common/no-fabrication.md +4 -12
- package/rules/common/patterns.md +3 -5
- package/rules/common/release-gates.md +3 -8
- package/rules/common/research.md +3 -8
- package/rules/common/review-before-change.md +3 -9
- package/rules/common/security.md +3 -6
- package/rules/common/skill-composition.md +3 -7
- package/rules/common/testing.md +3 -6
- package/rules/golang/coding-style.md +1 -5
- package/rules/golang/hooks.md +1 -5
- package/rules/golang/patterns.md +1 -5
- package/rules/golang/security.md +1 -5
- package/rules/golang/testing.md +1 -5
- package/rules/python/coding-style.md +1 -5
- package/rules/python/hooks.md +1 -5
- package/rules/python/patterns.md +1 -5
- package/rules/python/security.md +1 -5
- package/rules/python/testing.md +1 -5
- package/rules/swift/coding-style.md +1 -5
- package/rules/swift/hooks.md +1 -5
- package/rules/swift/patterns.md +1 -5
- package/rules/swift/security.md +1 -5
- package/rules/swift/testing.md +1 -5
- package/rules/typescript/coding-style.md +1 -5
- package/rules/typescript/hooks.md +1 -5
- package/rules/typescript/patterns.md +1 -5
- package/rules/typescript/security.md +1 -5
- package/rules/typescript/testing.md +1 -5
- package/rules/web/coding-style.md +3 -5
- package/rules/web/design-quality.md +3 -5
- package/rules/web/hooks.md +3 -5
- package/rules/web/patterns.md +3 -5
- package/rules/web/performance.md +3 -5
- package/rules/web/security.md +3 -5
- package/rules/web/testing.md +3 -5
- package/scripts/sync-specialists.mjs +20 -2
- package/skills/ai/agent-dev.md +4 -5
- package/skills/ai/llm-security.md +4 -5
- package/skills/ai/ml-ops.md +4 -5
- package/skills/ai/orchestration-workflow.md +4 -5
- package/skills/ai/prompt-and-eval.md +4 -5
- package/skills/ai/prompt-optimizer.md +4 -6
- package/skills/ai/rag-system.md +4 -5
- package/skills/architecture/api-design.md +4 -5
- package/skills/architecture/caching.md +4 -5
- package/skills/architecture/cloud-native.md +4 -5
- package/skills/architecture/message-queue.md +4 -5
- package/skills/architecture/security-arch.md +4 -5
- package/skills/compliance/ai-disclosure.md +4 -5
- package/skills/compliance/data-privacy.md +4 -5
- package/skills/compliance/license-audit.md +4 -5
- package/skills/compliance/regulatory-review.md +4 -5
- package/skills/development/cpp.md +4 -5
- package/skills/development/go.md +4 -5
- package/skills/development/java.md +4 -5
- package/skills/development/kotlin.md +4 -5
- package/skills/development/mobile-crossplatform.md +4 -5
- package/skills/development/python.md +4 -5
- package/skills/development/rust.md +4 -5
- package/skills/development/shell.md +4 -5
- package/skills/development/swift.md +4 -5
- package/skills/development/typescript.md +4 -5
- package/skills/devops/ci-cd.md +4 -5
- package/skills/devops/containerization.md +4 -5
- package/skills/devops/cost-optimization.md +4 -5
- package/skills/devops/data-engineering.md +4 -5
- package/skills/devops/database.md +4 -5
- package/skills/devops/dependency-management.md +4 -5
- package/skills/devops/devsecops.md +4 -5
- package/skills/devops/git-workflow.md +4 -5
- package/skills/devops/incident-response.md +4 -5
- package/skills/devops/monorepo.md +4 -5
- package/skills/devops/observability.md +4 -5
- package/skills/devops/performance.md +4 -5
- package/skills/devops/testing.md +4 -5
- package/skills/docs/adr-workflow.md +4 -7
- package/skills/docs/backlog-proposal-workflow.md +4 -3
- package/skills/docs/customer-profile-workflow.md +4 -3
- package/skills/docs/document-ingest-workflow.md +4 -3
- package/skills/docs/evidence-ingest-workflow.md +4 -3
- package/skills/docs/init-docs.md +4 -5
- package/skills/docs/init-project.md +4 -5
- package/skills/docs/prd-workflow.md +4 -7
- package/skills/docs/prfaq-workflow.md +4 -3
- package/skills/docs/product-intelligence-review.md +4 -3
- package/skills/docs/product-intelligence-workflow.md +4 -6
- package/skills/docs/product-signal-workflow.md +4 -5
- package/skills/docs/research-workflow.md +4 -5
- package/skills/docs/runbook-workflow.md +4 -5
- package/skills/docs/strategy-workflow.md +4 -5
- package/skills/exploration/dependency-graph-reading.md +4 -7
- package/skills/exploration/repo-map.md +4 -5
- package/skills/exploration/tracer-bullet-method.md +4 -7
- package/skills/exploration/unknown-codebase-onboarding.md +4 -7
- package/skills/frameworks/django.md +4 -5
- package/skills/frameworks/nextjs.md +4 -5
- package/skills/frameworks/react.md +4 -5
- package/skills/frameworks/spring-boot.md +4 -5
- package/skills/frontend-design/accessibility.md +4 -5
- package/skills/frontend-design/component-patterns.md +4 -5
- package/skills/frontend-design/engineering.md +4 -5
- package/skills/frontend-design/state-management.md +4 -5
- package/skills/frontend-design/ui-aesthetics.md +4 -5
- package/skills/frontend-design/ux-principles.md +4 -5
- package/skills/operating/change-management.md +4 -8
- package/skills/operating/incident-response.md +4 -8
- package/skills/operating/oncall-rotation.md +4 -7
- package/skills/operating/orchestration-reference.md +4 -10
- package/skills/quality-gates/review-work.md +4 -5
- package/skills/quality-gates/verify-change.md +4 -5
- package/skills/quality-gates/verify-module.md +4 -5
- package/skills/quality-gates/verify-quality.md +4 -5
- package/skills/quality-gates/verify-security.md +4 -5
- package/skills/roles/architect.ai-systems.md +6 -9
- package/skills/roles/architect.data.md +6 -9
- package/skills/roles/architect.enterprise.md +6 -9
- package/skills/roles/architect.integration.md +6 -9
- package/skills/roles/architect.md +7 -14
- package/skills/roles/architect.platform.md +6 -9
- package/skills/roles/data-analyst.experiment.md +6 -9
- package/skills/roles/data-analyst.md +6 -9
- package/skills/roles/data-analyst.product-intelligence.md +7 -9
- package/skills/roles/data-analyst.product.md +6 -9
- package/skills/roles/data-analyst.telemetry.md +7 -9
- package/skills/roles/data-engineer.pipeline.md +6 -9
- package/skills/roles/data-engineer.vector-retrieval.md +7 -9
- package/skills/roles/data-engineer.warehouse.md +6 -9
- package/skills/roles/debugger.md +6 -9
- package/skills/roles/designer.accessibility.md +6 -9
- package/skills/roles/designer.md +7 -9
- package/skills/roles/engineer.ai.md +6 -9
- package/skills/roles/engineer.data.md +6 -9
- package/skills/roles/engineer.md +9 -9
- package/skills/roles/engineer.platform.md +6 -9
- package/skills/roles/operator.docs.md +6 -9
- package/skills/roles/operator.md +9 -9
- package/skills/roles/operator.release.md +6 -9
- package/skills/roles/operator.sre.md +6 -9
- package/skills/roles/orchestrator.md +6 -9
- package/skills/roles/product-manager.ai-product.md +6 -9
- package/skills/roles/product-manager.business-strategy.md +6 -9
- package/skills/roles/product-manager.enterprise.md +6 -9
- package/skills/roles/product-manager.growth.md +7 -9
- package/skills/roles/product-manager.md +7 -9
- package/skills/roles/product-manager.platform.md +6 -9
- package/skills/roles/product-manager.product.md +6 -9
- package/skills/roles/qa.ai-eval.md +8 -9
- package/skills/roles/qa.api-contract.md +7 -9
- package/skills/roles/qa.data-pipeline.md +7 -9
- package/skills/roles/qa.md +7 -9
- package/skills/roles/qa.test-automation.md +6 -9
- package/skills/roles/qa.web-ui.md +7 -9
- package/skills/roles/researcher.explorer.md +6 -9
- package/skills/roles/researcher.md +8 -9
- package/skills/roles/researcher.ux.md +6 -9
- package/skills/roles/reviewer.devil-advocate.md +6 -9
- package/skills/roles/reviewer.evaluator.md +6 -9
- package/skills/roles/reviewer.md +9 -9
- package/skills/roles/reviewer.trace.md +6 -9
- package/skills/roles/security.ai.md +7 -9
- package/skills/roles/security.appsec.md +6 -9
- package/skills/roles/security.cloud.md +6 -9
- package/skills/roles/security.legal-compliance.md +6 -9
- package/skills/roles/security.md +7 -9
- package/skills/roles/security.privacy.md +7 -9
- package/skills/roles/security.supply-chain.md +7 -9
- package/skills/security/blue-team.md +4 -5
- package/skills/security/code-audit.md +4 -5
- package/skills/security/pentest.md +4 -5
- package/skills/security/red-team.md +4 -5
- package/skills/security/threat-intel.md +4 -5
- package/skills/security/vuln-research.md +4 -5
- package/skills/strategy/competitive-landscape.md +4 -8
- package/skills/strategy/market-research-methods.md +4 -8
- package/skills/strategy/narrative-arc.md +4 -8
- package/skills/strategy/pricing-positioning.md +4 -8
- package/skills/utility/clean-code.md +4 -5
- package/specialists/policy-inventory.json +14 -0
- package/lib/specialist-contracts-enforce.mjs +0 -172
- package/rules/common/agents.md +0 -28
- package/rules/common/development-workflow.md +0 -32
- package/rules/common/performance.md +0 -55
|
@@ -2,38 +2,130 @@
|
|
|
2
2
|
* lib/sync/skill-frontmatter.mjs — Build Anthropic Agent Skills frontmatter
|
|
3
3
|
* for SKILL.md files emitted by sync-specialists.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* extractSkillDescription pulls it out.
|
|
5
|
+
* Source skills carry YAML frontmatter with at minimum `name` + `description`
|
|
6
|
+
* (Anthropic spec). For role files, extra construct-internal keys (role,
|
|
7
|
+
* applies_to, inherits, version, profiles, cap) live in the same block; sync
|
|
8
|
+
* drops those at emit time so only spec-compliant keys ship in SKILL.md.
|
|
10
9
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
* Backwards-compat path: source files missing YAML frontmatter fall through
|
|
11
|
+
* to HTML-comment preamble extraction, then first body paragraph. Keeps
|
|
12
|
+
* unmigrated files working during transition; the validator warns until
|
|
13
|
+
* they migrate.
|
|
14
|
+
*
|
|
15
|
+
* Zero-dep posture: runs from isolated environments (cpSync'd tmpdirs
|
|
16
|
+
* without node_modules), so the YAML reader/emitter stays in-tree. Surface
|
|
17
|
+
* is small — read flat top-level keys + simple inline arrays; write 2 keys
|
|
18
|
+
* (name, description). For richer YAML, the migration script and validator
|
|
19
|
+
* use js-yaml.
|
|
13
20
|
*/
|
|
14
21
|
|
|
22
|
+
const FRONTMATTER_RE = /^---\n([\s\S]*?)\n---\n?/;
|
|
15
23
|
const HTML_COMMENT_RE = /^<!--([\s\S]*?)-->/;
|
|
16
|
-
// Matches the source skill comment header shape: `skills/path/foo.md (Title)
|
|
17
|
-
// Use when X.\n...rest of comment...`. Group 1 captures the optional title,
|
|
18
|
-
// group 2 captures all subsequent prose inside the comment. No /m flag — `$`
|
|
19
|
-
// matches end of input, not end of line, so multi-line "Use when..." prose
|
|
20
|
-
// is captured in full and firstSentence() picks the first terminator.
|
|
21
|
-
|
|
22
24
|
const SOURCE_HEADER_RE = /^skills\/[\w./-]+\s*(?:\(([^)]+)\))?\s*([\s\S]*?)$/;
|
|
23
25
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
26
|
+
const DESCRIPTION_MAX = 1024;
|
|
27
|
+
|
|
28
|
+
// In-tree minimal YAML parser. Handles the subset Construct skill frontmatter
|
|
29
|
+
// uses: flat scalar keys, double/single-quoted or unquoted strings, simple
|
|
30
|
+
// inline arrays ([a, b, c]), and block-style arrays (- item per line).
|
|
31
|
+
// Unparseable input returns null; callers fall back to extraction.
|
|
32
|
+
|
|
33
|
+
function parseFlatYaml(text) {
|
|
34
|
+
if (!text || typeof text !== 'string') return null;
|
|
35
|
+
const lines = text.split('\n');
|
|
36
|
+
const out = {};
|
|
37
|
+
let pendingArrayKey = null;
|
|
38
|
+
try {
|
|
39
|
+
for (const raw of lines) {
|
|
40
|
+
const line = raw.replace(/\r$/, '');
|
|
41
|
+
if (!line.trim() || line.trim().startsWith('#')) continue;
|
|
42
|
+
if (pendingArrayKey && /^\s+-\s+/.test(line)) {
|
|
43
|
+
out[pendingArrayKey].push(parseScalar(line.replace(/^\s+-\s+/, '')));
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
pendingArrayKey = null;
|
|
47
|
+
const m = line.match(/^([A-Za-z_][\w-]*)\s*:\s*(.*)$/);
|
|
48
|
+
if (!m) continue;
|
|
49
|
+
const key = m[1];
|
|
50
|
+
const rawValue = m[2];
|
|
51
|
+
if (rawValue === '' || rawValue === undefined) {
|
|
52
|
+
out[key] = [];
|
|
53
|
+
pendingArrayKey = key;
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
if (rawValue.startsWith('[') && rawValue.endsWith(']')) {
|
|
57
|
+
const inner = rawValue.slice(1, -1).trim();
|
|
58
|
+
out[key] = inner === '' ? [] : inner.split(',').map((s) => parseScalar(s.trim()));
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
out[key] = parseScalar(rawValue);
|
|
62
|
+
}
|
|
63
|
+
} catch (err) {
|
|
64
|
+
if (err instanceof YamlError) return null;
|
|
65
|
+
throw err;
|
|
66
|
+
}
|
|
67
|
+
return out;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
class YamlError extends Error {}
|
|
71
|
+
|
|
72
|
+
function parseScalar(s) {
|
|
73
|
+
const trimmed = s.trim();
|
|
74
|
+
if (trimmed === '' || trimmed === 'null' || trimmed === '~') return null;
|
|
75
|
+
if (trimmed === 'true') return true;
|
|
76
|
+
if (trimmed === 'false') return false;
|
|
77
|
+
if (/^-?\d+$/.test(trimmed)) return Number(trimmed);
|
|
78
|
+
if (/^-?\d+\.\d+$/.test(trimmed)) return Number(trimmed);
|
|
79
|
+
if (trimmed.startsWith('"')) {
|
|
80
|
+
if (!trimmed.endsWith('"') || trimmed.length < 2) throw new YamlError('unclosed double quote');
|
|
81
|
+
return trimmed.slice(1, -1).replace(/\\"/g, '"');
|
|
82
|
+
}
|
|
83
|
+
if (trimmed.startsWith("'")) {
|
|
84
|
+
if (!trimmed.endsWith("'") || trimmed.length < 2) throw new YamlError('unclosed single quote');
|
|
85
|
+
return trimmed.slice(1, -1).replace(/\\'/g, "'");
|
|
86
|
+
}
|
|
87
|
+
return trimmed;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function emitScalar(value) {
|
|
91
|
+
if (value === null || value === undefined) return 'null';
|
|
92
|
+
if (typeof value === 'boolean' || typeof value === 'number') return String(value);
|
|
93
|
+
const s = String(value);
|
|
94
|
+
if (/[:"'\n#&*!|>%@`]|^[-?]/.test(s) || s.length === 0) {
|
|
95
|
+
return `"${s.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;
|
|
96
|
+
}
|
|
97
|
+
return s;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function readSkillFrontmatter(content) {
|
|
101
|
+
if (!content) return null;
|
|
102
|
+
const match = content.match(FRONTMATTER_RE);
|
|
103
|
+
if (!match) return null;
|
|
104
|
+
return parseFlatYaml(match[1]);
|
|
105
|
+
}
|
|
27
106
|
|
|
28
107
|
export function buildSkillFrontmatter(name, sourceContent) {
|
|
29
|
-
const
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
108
|
+
const fromSource = readSkillFrontmatter(sourceContent);
|
|
109
|
+
const skillName = (fromSource?.name && typeof fromSource.name === 'string')
|
|
110
|
+
? fromSource.name
|
|
111
|
+
: kebabFromPath(name);
|
|
112
|
+
const description = (fromSource?.description && typeof fromSource.description === 'string')
|
|
113
|
+
? fromSource.description
|
|
114
|
+
: (extractSkillDescription(sourceContent) || `Construct skill: ${skillName}`);
|
|
115
|
+
const safe = description.replace(/\n+/g, ' ').slice(0, DESCRIPTION_MAX).replace(/"/g, "'").trim();
|
|
116
|
+
return `---\nname: ${emitScalar(skillName)}\ndescription: ${emitScalar(safe)}\n---\n`;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function kebabFromPath(name) {
|
|
120
|
+
return String(name).replace(/[/.]/g, '-').toLowerCase();
|
|
33
121
|
}
|
|
34
122
|
|
|
35
123
|
export function extractSkillDescription(content) {
|
|
36
124
|
if (!content) return null;
|
|
125
|
+
const fromYaml = readSkillFrontmatter(content);
|
|
126
|
+
if (fromYaml?.description && typeof fromYaml.description === 'string') {
|
|
127
|
+
return fromYaml.description;
|
|
128
|
+
}
|
|
37
129
|
const commentMatch = content.match(HTML_COMMENT_RE);
|
|
38
130
|
if (commentMatch) {
|
|
39
131
|
const inner = commentMatch[1].trim();
|
|
@@ -45,7 +137,7 @@ export function extractSkillDescription(content) {
|
|
|
45
137
|
const lines = inner.split('\n').map((l) => l.trim()).filter(Boolean);
|
|
46
138
|
if (lines.length >= 2) return firstSentence(lines.slice(1).join(' '));
|
|
47
139
|
}
|
|
48
|
-
const body = content.replace(HTML_COMMENT_RE, '').trim();
|
|
140
|
+
const body = content.replace(FRONTMATTER_RE, '').replace(HTML_COMMENT_RE, '').trim();
|
|
49
141
|
const paragraphs = body.split(/\n\s*\n/);
|
|
50
142
|
for (const p of paragraphs) {
|
|
51
143
|
const trimmed = p.trim();
|
|
@@ -26,6 +26,7 @@ import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSy
|
|
|
26
26
|
import { join } from 'node:path';
|
|
27
27
|
|
|
28
28
|
import { extractBeadsFromPlan, syncPlanWithBeads } from './beads-automation.mjs';
|
|
29
|
+
import { assertBeadId } from './beads-client.mjs';
|
|
29
30
|
import { buildPlanTemplate } from './project-init-shared.mjs';
|
|
30
31
|
|
|
31
32
|
const CONTEXT_RECENT_DAYS = 30;
|
|
@@ -191,6 +192,7 @@ export async function closeBeadsFromPrRefs({ prNumber, mergeCommitSha = '', cwd
|
|
|
191
192
|
: `Merged via PR #${prNumber}`;
|
|
192
193
|
|
|
193
194
|
for (const id of beadIds) {
|
|
195
|
+
try { assertBeadId(id); } catch (err) { errors.push({ id, reason: 'invalid-bead-id', detail: err.message }); continue; }
|
|
194
196
|
const status = readBeadStatus(cwd, id);
|
|
195
197
|
if (status === 'closed') { skipped.push(id); continue; }
|
|
196
198
|
if (status === null) { errors.push({ id, reason: 'bd-show-failed' }); continue; }
|
package/lib/update.mjs
CHANGED
|
@@ -10,6 +10,8 @@ import fs from 'node:fs';
|
|
|
10
10
|
import path from 'node:path';
|
|
11
11
|
import { spawnSync } from 'node:child_process';
|
|
12
12
|
|
|
13
|
+
import { writeVersionStamp } from './maintenance/cleanup.mjs';
|
|
14
|
+
|
|
13
15
|
const PACKAGE_NAME = '@geraldmaron/construct';
|
|
14
16
|
|
|
15
17
|
function readPackageJson(dir) {
|
|
@@ -70,6 +72,15 @@ export function buildUpdatePlan({ cwd }) {
|
|
|
70
72
|
args: [binPath, 'sync', '--no-docs'],
|
|
71
73
|
cwd: sourceRoot,
|
|
72
74
|
},
|
|
75
|
+
// Sweep legacy cx-*.md/.toml/.prompt.md left over from older releases
|
|
76
|
+
// at user scope, so the next doctor doesn't surface the warning.
|
|
77
|
+
{
|
|
78
|
+
label: 'Sweep legacy agents at user scope',
|
|
79
|
+
command: process.execPath,
|
|
80
|
+
args: [binPath, 'doctor', '--fix-legacy-agents'],
|
|
81
|
+
cwd: sourceRoot,
|
|
82
|
+
optional: true,
|
|
83
|
+
},
|
|
73
84
|
{
|
|
74
85
|
label: 'Run health checks',
|
|
75
86
|
command: process.execPath,
|
|
@@ -84,14 +95,24 @@ function formatCommand(command, args) {
|
|
|
84
95
|
return [command, ...args].join(' ');
|
|
85
96
|
}
|
|
86
97
|
|
|
87
|
-
function runStep(step, { spawn, env }) {
|
|
98
|
+
function runStep(step, { spawn, env, stdout }) {
|
|
88
99
|
const result = spawn(step.command, step.args, {
|
|
89
100
|
cwd: step.cwd,
|
|
90
101
|
env,
|
|
91
102
|
stdio: 'inherit',
|
|
92
103
|
});
|
|
93
|
-
if (result?.error)
|
|
104
|
+
if (result?.error) {
|
|
105
|
+
if (step.optional) {
|
|
106
|
+
stdout?.write?.(` (skipped: ${result.error.message})\n`);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
throw result.error;
|
|
110
|
+
}
|
|
94
111
|
if ((result?.status ?? 1) !== 0) {
|
|
112
|
+
if (step.optional) {
|
|
113
|
+
stdout?.write?.(` (skipped: exit ${result?.status ?? 1})\n`);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
95
116
|
throw new Error(`${step.label} failed with exit code ${result.status ?? 1}`);
|
|
96
117
|
}
|
|
97
118
|
}
|
|
@@ -107,9 +128,16 @@ export function runUpdate({ cwd, env = process.env, spawn = spawnSync, stdout =
|
|
|
107
128
|
for (const step of plan.steps) {
|
|
108
129
|
stdout.write(`→ ${step.label}\n`);
|
|
109
130
|
stdout.write(` ${formatCommand(step.command, step.args)}\n`);
|
|
110
|
-
runStep(step, { spawn, env });
|
|
131
|
+
runStep(step, { spawn, env, stdout });
|
|
111
132
|
}
|
|
112
133
|
|
|
134
|
+
// Advance the cleanup stamp to the checkout version so the next CLI
|
|
135
|
+
// invocation doesn't run a full cleanup pass on the assumption that
|
|
136
|
+
// an upgrade just happened.
|
|
137
|
+
try {
|
|
138
|
+
writeVersionStamp({ version: plan.version, summary: { freedBytes: 0, durationMs: 0 } });
|
|
139
|
+
} catch { /* stamp write is best-effort */ }
|
|
140
|
+
|
|
113
141
|
stdout.write('\n✓ Construct updated from current checkout.\n');
|
|
114
142
|
return plan;
|
|
115
143
|
}
|
package/lib/upgrade.mjs
CHANGED
|
@@ -10,6 +10,8 @@ import path from 'node:path';
|
|
|
10
10
|
import { spawnSync } from 'node:child_process';
|
|
11
11
|
import { fileURLToPath } from 'node:url';
|
|
12
12
|
|
|
13
|
+
import { writeVersionStamp } from './maintenance/cleanup.mjs';
|
|
14
|
+
|
|
13
15
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
14
16
|
const ROOT_DIR = path.resolve(__dirname, '..');
|
|
15
17
|
const PACKAGE_NAME = '@geraldmaron/construct';
|
|
@@ -97,7 +99,7 @@ export function runUpgrade({ cwd = process.cwd(), env = process.env, stdout = pr
|
|
|
97
99
|
}
|
|
98
100
|
|
|
99
101
|
stdout.write('Upgrading...\n\n');
|
|
100
|
-
|
|
102
|
+
|
|
101
103
|
const steps = [
|
|
102
104
|
{
|
|
103
105
|
label: 'Fetching latest from npm',
|
|
@@ -109,6 +111,15 @@ export function runUpgrade({ cwd = process.cwd(), env = process.env, stdout = pr
|
|
|
109
111
|
command: 'construct',
|
|
110
112
|
args: ['sync', '--no-docs'],
|
|
111
113
|
},
|
|
114
|
+
// Sweep legacy cx-*.md/.toml/.prompt.md left behind by older releases at
|
|
115
|
+
// user scope. Without this the user gets a doctor warning every session
|
|
116
|
+
// until they manually run `construct doctor --fix-legacy-agents`.
|
|
117
|
+
{
|
|
118
|
+
label: 'Sweeping legacy agents at user scope',
|
|
119
|
+
command: 'construct',
|
|
120
|
+
args: ['doctor', '--fix-legacy-agents'],
|
|
121
|
+
optional: true,
|
|
122
|
+
},
|
|
112
123
|
{
|
|
113
124
|
label: 'Running health checks',
|
|
114
125
|
command: 'construct',
|
|
@@ -123,18 +134,34 @@ export function runUpgrade({ cwd = process.cwd(), env = process.env, stdout = pr
|
|
|
123
134
|
env,
|
|
124
135
|
stdio: 'inherit',
|
|
125
136
|
});
|
|
126
|
-
|
|
137
|
+
|
|
127
138
|
if (result.error) {
|
|
139
|
+
if (step.optional) {
|
|
140
|
+
stdout.write(` (skipped: ${result.error.message})\n`);
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
128
143
|
stdout.write(`\n✗ ${step.label} failed: ${result.error.message}\n`);
|
|
129
144
|
return { success: false, reason: 'step_failed', step: step.label };
|
|
130
145
|
}
|
|
131
|
-
|
|
146
|
+
|
|
132
147
|
if (result.status !== 0) {
|
|
148
|
+
if (step.optional) {
|
|
149
|
+
stdout.write(` (skipped: exit ${result.status})\n`);
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
133
152
|
stdout.write(`\n✗ ${step.label} failed with exit code ${result.status}\n`);
|
|
134
153
|
return { success: false, reason: 'step_failed', step: step.label };
|
|
135
154
|
}
|
|
136
155
|
}
|
|
137
|
-
|
|
156
|
+
|
|
157
|
+
// Advance the cleanup stamp so the next CLI invocation doesn't re-run a
|
|
158
|
+
// full cleanup pass under the assumption that the user just upgraded.
|
|
159
|
+
// Without this, every command pays the cleanup cost until something
|
|
160
|
+
// else writes the stamp.
|
|
161
|
+
try {
|
|
162
|
+
writeVersionStamp({ version: latestVersion, summary: { freedBytes: 0, durationMs: 0 } });
|
|
163
|
+
} catch { /* stamp write is best-effort */ }
|
|
164
|
+
|
|
138
165
|
stdout.write('\n');
|
|
139
166
|
stdout.write(`✓ Upgraded to ${latestVersion}\n`);
|
|
140
167
|
return { success: true, upgraded: true, from: currentVersion, to: latestVersion };
|
|
@@ -1,62 +1,71 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* lib/validators/skills.mjs — Validate the structure of skill files.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* skill paths) that follow this convention:
|
|
4
|
+
* After the YAML-frontmatter migration, every skill file under `skills/` has:
|
|
6
5
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
6
|
+
* ---
|
|
7
|
+
* name: <kebab-case>
|
|
8
|
+
* description: "<≤1024 chars, includes a 'use when' trigger>"
|
|
9
|
+
* [role/applies_to/inherits/version/profiles/cap for role files]
|
|
10
|
+
* ---
|
|
11
11
|
* # <Title>
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* ...
|
|
12
|
+
* Use when: <trigger condition>
|
|
13
|
+
* <body>
|
|
15
14
|
*
|
|
16
15
|
* The validator splits findings into hard errors (block the build) and soft
|
|
17
16
|
* warnings (surfaced by `construct doctor` without failing it):
|
|
18
17
|
*
|
|
19
18
|
* Hard errors:
|
|
20
|
-
* - missing
|
|
19
|
+
* - frontmatter missing or not parseable as YAML
|
|
20
|
+
* - name missing, > 64 chars, fails ^[a-z0-9][a-z0-9-]*$, or contains
|
|
21
|
+
* reserved tokens (anthropic, claude)
|
|
22
|
+
* - description missing, empty, > 1024 chars, contains XML/HTML tags,
|
|
23
|
+
* or missing a "use when" trigger clause
|
|
24
|
+
* - missing H1 title (in body)
|
|
21
25
|
* - title over 80 chars
|
|
22
26
|
* - duplicate relative paths across roots
|
|
23
27
|
* - unreadable file
|
|
24
28
|
*
|
|
25
29
|
* Soft warnings:
|
|
26
|
-
* - opener
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* - opener over 240 chars
|
|
30
|
-
*
|
|
31
|
-
* Hard checks prevent broken file structure; soft checks surface drift from
|
|
32
|
-
* authoring conventions without blocking authors who legitimately diverge.
|
|
30
|
+
* - body opener missing or doesn't match a "Use this skill when/to/for ..."
|
|
31
|
+
* form (YAML description is now primary; body opener is a behavioral hint)
|
|
32
|
+
* - body opener over 240 chars
|
|
33
33
|
*/
|
|
34
34
|
|
|
35
35
|
import { readdirSync, readFileSync, statSync } from 'node:fs';
|
|
36
36
|
import path from 'node:path';
|
|
37
|
+
import yaml from 'js-yaml';
|
|
37
38
|
|
|
38
|
-
const
|
|
39
|
+
const DESCRIPTION_MAX = 1024;
|
|
40
|
+
const NAME_MAX = 64;
|
|
39
41
|
const TITLE_MAX_CHARS = 80;
|
|
42
|
+
const BODY_OPENER_MAX = 240;
|
|
43
|
+
const NAME_RE = /^[a-z0-9][a-z0-9-]*$/;
|
|
44
|
+
const FRONTMATTER_RE = /^---\n([\s\S]*?)\n---\n?/;
|
|
40
45
|
|
|
41
|
-
// Accept any of the conventional skill-trigger openers actually in use across
|
|
42
|
-
// the corpus: "Use this skill when/to/for", "Use when:", "Use this when",
|
|
43
|
-
// "Trigger:", or a leading "When ..." that signals a triggering condition.
|
|
44
46
|
const TRIGGER_PATTERN = /^(?:use\s+(?:this\s+(?:skill\s+)?)?(?:when|to|for|if)\b|when\s+to\s+use\b|trigger\s*:|use\s+when\s*:)/i;
|
|
45
47
|
|
|
46
48
|
function* walk(root) {
|
|
47
|
-
const
|
|
48
|
-
for (const entry of entries) {
|
|
49
|
+
for (const entry of readdirSync(root, { withFileTypes: true })) {
|
|
49
50
|
const full = path.join(root, entry.name);
|
|
50
|
-
if (entry.isDirectory())
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
51
|
+
if (entry.isDirectory()) yield* walk(full);
|
|
52
|
+
else if (entry.isFile() && entry.name.endsWith('.md')) yield full;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function parseFrontmatter(content) {
|
|
57
|
+
const match = content.match(FRONTMATTER_RE);
|
|
58
|
+
if (!match) return { frontmatter: null, body: content };
|
|
59
|
+
try {
|
|
60
|
+
const parsed = yaml.load(match[1]) || {};
|
|
61
|
+
return { frontmatter: parsed, body: content.slice(match[0].length) };
|
|
62
|
+
} catch (err) {
|
|
63
|
+
return { frontmatter: null, body: content.slice(match[0].length), yamlError: err.message };
|
|
55
64
|
}
|
|
56
65
|
}
|
|
57
66
|
|
|
58
|
-
function extractTitleAndOpener(
|
|
59
|
-
const lines =
|
|
67
|
+
function extractTitleAndOpener(body) {
|
|
68
|
+
const lines = body.split(/\r?\n/);
|
|
60
69
|
let title = null;
|
|
61
70
|
let opener = null;
|
|
62
71
|
let inBody = false;
|
|
@@ -64,11 +73,7 @@ function extractTitleAndOpener(text) {
|
|
|
64
73
|
const line = lines[i].trim();
|
|
65
74
|
if (!title) {
|
|
66
75
|
const m = line.match(/^#\s+(.+?)\s*$/);
|
|
67
|
-
if (m) {
|
|
68
|
-
title = m[1];
|
|
69
|
-
inBody = true;
|
|
70
|
-
continue;
|
|
71
|
-
}
|
|
76
|
+
if (m) { title = m[1]; inBody = true; continue; }
|
|
72
77
|
} else if (inBody && line && !line.startsWith('#') && !line.startsWith('<!--')) {
|
|
73
78
|
opener = line;
|
|
74
79
|
break;
|
|
@@ -77,23 +82,31 @@ function extractTitleAndOpener(text) {
|
|
|
77
82
|
return { title, opener };
|
|
78
83
|
}
|
|
79
84
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
85
|
+
function validateName(name) {
|
|
86
|
+
if (!name) return 'frontmatter.name is missing';
|
|
87
|
+
if (typeof name !== 'string') return `frontmatter.name must be a string (got ${typeof name})`;
|
|
88
|
+
if (!NAME_RE.test(name)) return `frontmatter.name "${name}" must match ${NAME_RE}`;
|
|
89
|
+
if (name.length > NAME_MAX) return `frontmatter.name "${name}" exceeds ${NAME_MAX} chars`;
|
|
90
|
+
if (/\banthropic\b|\bclaude\b/.test(name)) return `frontmatter.name "${name}" contains reserved token`;
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function validateDescription(description) {
|
|
95
|
+
if (!description) return 'frontmatter.description is missing or empty';
|
|
96
|
+
if (typeof description !== 'string') return `frontmatter.description must be a string (got ${typeof description})`;
|
|
97
|
+
if (description.length > DESCRIPTION_MAX) return `frontmatter.description ${description.length} > ${DESCRIPTION_MAX} chars`;
|
|
98
|
+
if (/<[A-Za-z][^>]*>/.test(description)) return 'frontmatter.description contains XML/HTML tags';
|
|
99
|
+
if (!/use\s+when|use\s+this/i.test(description)) return 'frontmatter.description missing "use when" trigger clause';
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
|
|
91
103
|
export function validateSkills(roots) {
|
|
92
104
|
const dirs = Array.isArray(roots) ? roots : [roots];
|
|
93
105
|
const errors = [];
|
|
94
106
|
const warnings = [];
|
|
95
107
|
const skills = [];
|
|
96
108
|
const seenRelative = new Map();
|
|
109
|
+
const seenNames = new Map();
|
|
97
110
|
|
|
98
111
|
for (const dir of dirs) {
|
|
99
112
|
let exists = false;
|
|
@@ -113,12 +126,31 @@ export function validateSkills(roots) {
|
|
|
113
126
|
|
|
114
127
|
let raw;
|
|
115
128
|
try { raw = readFileSync(filePath, 'utf8'); }
|
|
116
|
-
catch (err) {
|
|
117
|
-
|
|
129
|
+
catch (err) { errors.push(`${rel}: cannot read (${err.message})`); continue; }
|
|
130
|
+
|
|
131
|
+
const { frontmatter, body, yamlError } = parseFrontmatter(raw);
|
|
132
|
+
|
|
133
|
+
if (yamlError) {
|
|
134
|
+
errors.push(`${rel}: frontmatter YAML parse error — ${yamlError}`);
|
|
118
135
|
continue;
|
|
119
136
|
}
|
|
137
|
+
if (!frontmatter) {
|
|
138
|
+
errors.push(`${rel}: missing YAML frontmatter block (---name/description---)`);
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const nameErr = validateName(frontmatter.name);
|
|
143
|
+
if (nameErr) errors.push(`${rel}: ${nameErr}`);
|
|
144
|
+
else {
|
|
145
|
+
const dupName = seenNames.get(frontmatter.name);
|
|
146
|
+
if (dupName) errors.push(`${rel}: duplicate frontmatter.name "${frontmatter.name}" (also in ${dupName})`);
|
|
147
|
+
else seenNames.set(frontmatter.name, rel);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const descErr = validateDescription(frontmatter.description);
|
|
151
|
+
if (descErr) errors.push(`${rel}: ${descErr}`);
|
|
120
152
|
|
|
121
|
-
const { title, opener } = extractTitleAndOpener(
|
|
153
|
+
const { title, opener } = extractTitleAndOpener(body);
|
|
122
154
|
|
|
123
155
|
if (!title) {
|
|
124
156
|
errors.push(`${rel}: missing H1 title (e.g. "# Skill Name")`);
|
|
@@ -127,14 +159,14 @@ export function validateSkills(roots) {
|
|
|
127
159
|
}
|
|
128
160
|
|
|
129
161
|
if (!opener) {
|
|
130
|
-
warnings.push(`${rel}: no trigger opener after the H1
|
|
162
|
+
warnings.push(`${rel}: no trigger opener in body after the H1 — YAML description still primary, but body opener helps in-task behavior`);
|
|
131
163
|
} else if (!TRIGGER_PATTERN.test(opener)) {
|
|
132
|
-
warnings.push(`${rel}: opener should start with "Use this skill when/to/for ..." (got "${opener.slice(0, 60)}…")`);
|
|
133
|
-
} else if (opener.length >
|
|
134
|
-
warnings.push(`${rel}: opener exceeds ${
|
|
164
|
+
warnings.push(`${rel}: body opener should start with "Use this skill when/to/for ..." (got "${opener.slice(0, 60)}…")`);
|
|
165
|
+
} else if (opener.length > BODY_OPENER_MAX) {
|
|
166
|
+
warnings.push(`${rel}: body opener exceeds ${BODY_OPENER_MAX} chars (got ${opener.length})`);
|
|
135
167
|
}
|
|
136
168
|
|
|
137
|
-
skills.push({ path: rel, title, opener });
|
|
169
|
+
skills.push({ path: rel, name: frontmatter.name, description: frontmatter.description, title, opener });
|
|
138
170
|
}
|
|
139
171
|
}
|
|
140
172
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@geraldmaron/construct",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.16",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Construct — agent orchestration layer for OpenCode, Claude Code, and other coding surfaces",
|
|
6
6
|
"bin": {
|
|
@@ -57,7 +57,6 @@
|
|
|
57
57
|
"lint:contracts": "node ./bin/construct lint:contracts",
|
|
58
58
|
"lint:agents": "node ./bin/construct lint:agents",
|
|
59
59
|
"build:sea": "esbuild bin/construct --bundle --platform=node --target=node20 --packages=external --outfile=dist/construct-bundle.cjs 2>&1 || (echo 'esbuild not found — install with: npm install -g esbuild' && exit 1)",
|
|
60
|
-
"lint:prose": "node scripts/lint-prose.mjs",
|
|
61
60
|
"lint:profiles": "node scripts/lint-profiles.mjs",
|
|
62
61
|
"learning:status": "node scripts/learning-status.mjs",
|
|
63
62
|
"lint:templates": "node scripts/lint-commits-pr.mjs",
|
|
@@ -73,16 +72,17 @@
|
|
|
73
72
|
"@huggingface/transformers": "^4.2.0",
|
|
74
73
|
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
75
74
|
"@xenova/transformers": "^2.0.1",
|
|
75
|
+
"js-yaml": "^4.2.0",
|
|
76
76
|
"node-webvtt": "^1.9.3",
|
|
77
77
|
"postgres": "^3.4.9"
|
|
78
78
|
},
|
|
79
79
|
"optionalDependencies": {
|
|
80
80
|
"@opentelemetry/api": "^1.9.0",
|
|
81
|
-
"@opentelemetry/
|
|
81
|
+
"@opentelemetry/core": "^1.25.0",
|
|
82
82
|
"@opentelemetry/exporter-trace-otlp-http": "^0.52.0",
|
|
83
83
|
"@opentelemetry/resources": "^1.25.0",
|
|
84
|
-
"@opentelemetry/
|
|
85
|
-
"@opentelemetry/
|
|
84
|
+
"@opentelemetry/sdk-trace-node": "^1.25.0",
|
|
85
|
+
"@opentelemetry/semantic-conventions": "^1.25.0"
|
|
86
86
|
},
|
|
87
87
|
"overrides": {
|
|
88
88
|
"express-rate-limit": "8.5.1",
|
package/personas/construct.md
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
routing rules, approval boundaries, and output contract. Loaded by sync-specialists
|
|
6
|
-
and emitted to every supported platform.
|
|
7
|
-
-->
|
|
1
|
+
---
|
|
2
|
+
name: construct
|
|
3
|
+
description: Construct persona prompt.
|
|
4
|
+
---
|
|
8
5
|
You are Construct. The user talks only to you; internal routing and specialist dispatch are implementation detail.
|
|
9
6
|
|
|
10
7
|
**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`.
|
|
@@ -37,6 +34,8 @@ Execution model:
|
|
|
37
34
|
|
|
38
35
|
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.
|
|
39
36
|
|
|
37
|
+
Orchestrated dispatches emit a task-packet with `goal`, `intent`, `workCategory`, `riskFlags`, `acceptanceCriteria` before naming specialists (`specialists/contracts.json:construct-to-orchestrator`).
|
|
38
|
+
|
|
40
39
|
## Gates and contracts (org-in-a-box)
|
|
41
40
|
|
|
42
41
|
`routeRequest` returns three artifacts; honor all three:
|