@geraldmaron/construct 1.2.0 → 1.2.1

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.
@@ -58,7 +58,7 @@ import {
58
58
  profileCreate, profileArchive, sandboxList, learningStatus,
59
59
  knowledgeGraphAsk,
60
60
  } from './tools/profile.mjs';
61
- import { modelResolve, triageRecommend, workflowInvoke, capabilityDescribe, executionResolve } from './tools/embedded-contract.mjs';
61
+ import { modelResolve, triageRecommend, workflowInvoke, capabilityDescribe, executionResolve, artifactWorkflow } from './tools/embedded-contract.mjs';
62
62
 
63
63
  const DEFAULT_ROOT_DIR = resolve(dirname(fileURLToPath(import.meta.url)), '..', '..');
64
64
  const ROOT_DIR = resolve(process.env.CX_TOOLKIT_DIR || DEFAULT_ROOT_DIR);
@@ -1134,6 +1134,23 @@ const ALL_TOOL_DEFS = [
1134
1134
  },
1135
1135
  },
1136
1136
  },
1137
+ {
1138
+ name: 'artifact_workflow',
1139
+ description: 'Plan a manifest-backed document artifact workflow and return a truthful provenance report. It separates planned steps from locally executed validation/export and never claims a host-planned specialist review or rewrite was completed. Durable local export requires approval_mode=allow-durable-write.',
1140
+ inputSchema: {
1141
+ type: 'object',
1142
+ properties: {
1143
+ input: { type: 'string', description: 'Natural-language artifact request.' },
1144
+ artifact_type: { type: 'string', description: 'Registered manifest document class.' },
1145
+ file_path: { type: 'string', description: 'Optional existing source document for local validation/export.' },
1146
+ format: { type: 'string', description: 'Distribution format such as pdf, docx, html, deck, or pptx.' },
1147
+ output_path: { type: 'string', description: 'Optional output destination when locally exporting.' },
1148
+ branding: { type: 'string', enum: ['construct', 'plain'], description: 'Construct is default; plain is an explicit opt-out.' },
1149
+ overrides: { type: 'object', description: 'Per-invocation manifest workflow overrides.' },
1150
+ approval_mode: { type: 'string', enum: ['proposal-only', 'requires-human-approval', 'allow-durable-write'], description: 'Only allow-durable-write performs local validation/export.' },
1151
+ },
1152
+ },
1153
+ },
1137
1154
  {
1138
1155
  name: 'model_resolve',
1139
1156
  description: 'Resolve which model an embedded Construct workflow should use given the host/IDE provider context. Precedence: host model → same-provider-family fallback → Construct tier default → structured config error. Never reads or returns credential values (requiresCredential is a boolean) and never claims unverified provider health. Read-only; performs no writes.',
@@ -1421,6 +1438,7 @@ export async function dispatchToolByName(name, args = {}) {
1421
1438
  else if (name === 'workflow_invoke') result = await workflowInvoke(args);
1422
1439
  else if (name === 'capability_describe') result = capabilityDescribe(args);
1423
1440
  else if (name === 'construct_execution_resolve') result = executionResolve(args);
1441
+ else if (name === 'artifact_workflow') result = artifactWorkflow(args);
1424
1442
  else if (name === 'orchestration_run') { const m = await import('./tools/orchestration-run.mjs'); result = await m.orchestrationRun(args); }
1425
1443
  else if (name === 'orchestration_status') { const m = await import('./tools/orchestration-run.mjs'); result = await m.orchestrationStatus(args); }
1426
1444
  else if (name === 'construct_call') {
@@ -14,6 +14,7 @@ import { invokeWorkflow as invokeWorkflowCore } from '../../embedded-contract/wo
14
14
  import { buildCapabilityContract } from '../../embedded-contract/capability.mjs';
15
15
  import { resolveExecution as resolveExecutionCore } from '../../embedded-contract/execution.mjs';
16
16
  import { resolveInput } from '../../embedded-contract/ingest.mjs';
17
+ import { runArtifactWorkflow as runArtifactWorkflowCore } from '../../artifact-workflow.mjs';
17
18
 
18
19
  async function ingestArgs(args) {
19
20
  if (args.input || !args.file_path) return { input: args.input, ingestion: undefined };
@@ -91,3 +92,16 @@ export function executionResolve(args = {}) {
91
92
  };
92
93
  return wrapContractResult(resolveExecutionCore(request), { surface: 'mcp' });
93
94
  }
95
+
96
+ export function artifactWorkflow(args = {}) {
97
+ return wrapContractResult(runArtifactWorkflowCore({
98
+ input: args.input || args.request,
99
+ artifactType: args.artifact_type,
100
+ filePath: args.file_path,
101
+ format: args.format,
102
+ outputPath: args.output_path,
103
+ branding: args.branding,
104
+ overrides: args.overrides,
105
+ approvalMode: args.approval_mode,
106
+ }), { surface: 'mcp' });
107
+ }
@@ -80,6 +80,8 @@ export function transcodeWebmToMp4(src, dest) {
80
80
  const result = spawnSync(ffmpeg, [
81
81
  '-y', '-i', src,
82
82
  '-c:v', 'libx264',
83
+ '-crf', '18',
84
+ '-preset', 'medium',
83
85
  '-pix_fmt', 'yuv420p',
84
86
  '-movflags', '+faststart',
85
87
  dest,
@@ -704,7 +704,9 @@ const server = createServer(async (req, res) => {
704
704
  const rateTier = url.pathname === '/api/chat/stream' || url.pathname === '/api/chat'
705
705
  || url.pathname === '/api/chat/loop/stream'
706
706
  ? 'chat'
707
- : (req.method !== 'GET' && req.method !== 'HEAD' ? 'write' : 'read');
707
+ : url.pathname === '/api/chat/loop/command'
708
+ ? 'command'
709
+ : (req.method !== 'GET' && req.method !== 'HEAD' ? 'write' : 'read');
708
710
  if (checkRateLimit) {
709
711
  const limit = checkRateLimit(req, rateTier, { env: process.env });
710
712
  if (!limit.allowed) {
@@ -1580,6 +1582,32 @@ const server = createServer(async (req, res) => {
1580
1582
  return;
1581
1583
  }
1582
1584
 
1585
+ if (url.pathname === '/api/sources' && req.method === 'GET') {
1586
+ try {
1587
+ const { loadProjectConfig } = await import('../config/project-config.mjs');
1588
+ const {
1589
+ resolveEffectiveSourceTargetsFromConfig,
1590
+ legacyEnvSourceTargets,
1591
+ normalizeConfigTarget,
1592
+ } = await import('../config/source-targets.mjs');
1593
+ const loaded = loadProjectConfig(ROOT_DIR, process.env);
1594
+ const configTargets = (loaded.config.sources?.targets ?? []).map(normalizeConfigTarget);
1595
+ const envTargets = legacyEnvSourceTargets(process.env);
1596
+ const effective = resolveEffectiveSourceTargetsFromConfig(loaded.config, process.env);
1597
+ res.writeHead(200, { 'Content-Type': 'application/json' });
1598
+ res.end(JSON.stringify({
1599
+ path: loaded.path,
1600
+ configTargets,
1601
+ envTargets,
1602
+ effective,
1603
+ }));
1604
+ } catch (err) {
1605
+ res.writeHead(500, { 'Content-Type': 'application/json' });
1606
+ res.end(JSON.stringify({ error: err.message }));
1607
+ }
1608
+ return;
1609
+ }
1610
+
1583
1611
  if (url.pathname === '/api/intake/config' && req.method === 'GET') {
1584
1612
  try {
1585
1613
  const { loadIntakeConfig, INTAKE_DEPTH_GUIDANCE, INTAKE_HARD_MAX_DEPTH } = await import('../intake/intake-config.mjs');
@@ -1592,7 +1620,7 @@ const server = createServer(async (req, res) => {
1592
1620
  rootDir: ROOT_DIR,
1593
1621
  guidance: INTAKE_DEPTH_GUIDANCE,
1594
1622
  hardMaxDepth: INTAKE_HARD_MAX_DEPTH,
1595
- defaults: { projectInbox: '.cx/inbox', docsIntake: 'docs/intake' },
1623
+ defaults: { projectInbox: '.cx/inbox', docsIntake: 'docs/intake', rootInbox: 'inbox/' },
1596
1624
  label: rebrand.intakeQueueLabel,
1597
1625
  itemNoun: rebrand.signalNoun,
1598
1626
  }));
@@ -2341,6 +2369,7 @@ const server = createServer(async (req, res) => {
2341
2369
  maxDepth: data.maxDepth,
2342
2370
  includeProjectInbox: data.includeProjectInbox,
2343
2371
  includeDocsIntake: data.includeDocsIntake,
2372
+ includeRootInbox: data.includeRootInbox ?? data.includeArchetypeInbox,
2344
2373
  });
2345
2374
  res.writeHead(200, { 'Content-Type': 'application/json' });
2346
2375
  res.end(JSON.stringify({ success: true, config: next }));
@@ -3,9 +3,10 @@
3
3
  *
4
4
  * Three tiers, configurable via env:
5
5
  *
6
- * read 60 req/min/key (dashboard polling, list endpoints)
7
- * chat 10 req/min/key (/api/chat/stream — bursty + expensive)
8
- * write 5 req/min/key (state-mutating endpoints approvals, config)
6
+ * read 60 req/min/key (dashboard polling, list endpoints)
7
+ * chat 10 req/min/key (/api/chat/stream — bursty + expensive)
8
+ * command 120 req/min/key (/api/chat/loop/commandslash cmds, no LLM)
9
+ * write 5 req/min/key (state-mutating endpoints — approvals, config)
9
10
  *
10
11
  * `key` is by default the request's source IP, with the active session id
11
12
  * mixed in when present so two users behind the same NAT get separate
@@ -18,6 +19,7 @@
18
19
  const DEFAULT_TIERS = {
19
20
  read: { capacity: 60, refillPerMs: 60 / 60_000 },
20
21
  chat: { capacity: 10, refillPerMs: 10 / 60_000 },
22
+ command: { capacity: 120, refillPerMs: 120 / 60_000 },
21
23
  write: { capacity: 5, refillPerMs: 5 / 60_000 },
22
24
  };
23
25
 
@@ -306,8 +306,9 @@ async function runSnapshotForSlack() {
306
306
  const { SnapshotEngine, renderMarkdown } = await import('../embed/snapshot.mjs');
307
307
  const { EMPTY_CONFIG } = await import('../embed/config.mjs');
308
308
 
309
- const registry = ProviderRegistry.fromEnv(env);
310
- const sources = registry.autoSources(env);
309
+ const registry = await ProviderRegistry.fromEnv(env);
310
+ const { resolveAutoEmbedSources } = await import('../embed/auto-sources.mjs');
311
+ const sources = resolveAutoEmbedSources({ cwd: process.cwd(), env, registry });
311
312
  if (!sources.length) return '⚠️ No embed sources configured. Add credentials to config.env.';
312
313
 
313
314
  const config = { ...EMPTY_CONFIG, sources, snapshot: { maxItems: 10 } };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geraldmaron/construct",
3
- "version": "1.2.0",
3
+ "version": "1.2.1",
4
4
  "type": "module",
5
5
  "packageManager": "npm@11.5.1",
6
6
  "description": "Construct — agent orchestration layer for OpenCode, Claude Code, and other coding surfaces",
@@ -1,7 +1,16 @@
1
1
  {
2
2
  "$schema": "./artifact-manifest.schema.json",
3
- "version": 1,
4
- "description": "Single source of truth for document types: template, owners, workflow skill, tone, structure, visuals, and release gates.",
3
+ "version": 2,
4
+ "description": "Single source of truth for registered document classes: template, author and reviewer chains, workflow skill, validation, output capability, tone, structure, visuals, and release gates.",
5
+ "workflowDefaults": {
6
+ "validation": {
7
+ "releaseGate": true
8
+ },
9
+ "outputs": {
10
+ "formats": ["pdf", "docx", "doc", "deck", "pptx", "html", "rtf", "odt", "epub", "tex", "txt", "md", "mdx"],
11
+ "branding": "construct"
12
+ }
13
+ },
5
14
  "artifacts": {
6
15
  "prd": {
7
16
  "template": "templates/docs/prd.md",
@@ -8,6 +8,7 @@
8
8
  "properties": {
9
9
  "version": { "type": "integer", "minimum": 1 },
10
10
  "description": { "type": "string" },
11
+ "workflowDefaults": { "$ref": "#/$defs/workflowDefaults" },
11
12
  "artifacts": {
12
13
  "type": "object",
13
14
  "additionalProperties": { "$ref": "#/$defs/artifactEntry" }
@@ -34,6 +35,29 @@
34
35
  "optionalReviewers": { "type": "array", "items": { "type": "string" } }
35
36
  }
36
37
  },
38
+ "workflowDefaults": {
39
+ "type": "object",
40
+ "properties": {
41
+ "authorChain": { "type": "array", "items": { "type": "string" } },
42
+ "reviewerChain": { "type": "array", "items": { "type": "string" } },
43
+ "validation": { "$ref": "#/$defs/validation" },
44
+ "outputs": { "$ref": "#/$defs/outputs" }
45
+ }
46
+ },
47
+ "validation": {
48
+ "type": "object",
49
+ "properties": {
50
+ "releaseGate": { "type": "boolean" },
51
+ "requiredChecks": { "type": "array", "items": { "type": "string" } }
52
+ }
53
+ },
54
+ "outputs": {
55
+ "type": "object",
56
+ "properties": {
57
+ "formats": { "type": "array", "items": { "type": "string" }, "minItems": 1 },
58
+ "branding": { "type": "string", "enum": ["construct", "plain"] }
59
+ }
60
+ },
37
61
  "artifactEntry": {
38
62
  "type": "object",
39
63
  "required": ["template", "primaryOwners", "toneDefault", "releaseGate"],
@@ -46,7 +70,13 @@
46
70
  "structureRequirements": { "type": "array", "items": { "type": "string" } },
47
71
  "visualRequirements": { "type": "array", "items": { "$ref": "#/$defs/visualRequirement" } },
48
72
  "researchProfile": { "type": ["string", "null"] },
49
- "releaseGate": { "$ref": "#/$defs/releaseGate" }
73
+ "releaseGate": { "$ref": "#/$defs/releaseGate" },
74
+ "documentClass": { "type": "string" },
75
+ "aliases": { "type": "array", "items": { "type": "string" } },
76
+ "authorChain": { "type": "array", "items": { "type": "string" } },
77
+ "reviewerChain": { "type": "array", "items": { "type": "string" } },
78
+ "validation": { "$ref": "#/$defs/validation" },
79
+ "outputs": { "$ref": "#/$defs/outputs" }
50
80
  }
51
81
  }
52
82
  }