@arjunkhera/atlas 0.3.2 → 0.3.3

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.
@@ -17,7 +17,7 @@ import { EngramHttpError } from './client.mjs';
17
17
  import { VerbError, refuse, canonicalJson, sha256, text, oneOf, origin, url, stringList, LIVE_LIFECYCLES, REPO_KINDS, LIVE_KINDS, repoKinds, kindsFromArgs } from './verb-fields.mjs';
18
18
  import { createQuestionVerbs, COSTS, QUESTION_WRITE_VERBS, QUESTION_READ_VERBS } from './questions.mjs';
19
19
  import { createDeliveryVerbs, DELIVERY_WRITE_VERBS, DELIVERY_READ_VERBS } from './delivery.mjs';
20
- import { createCapabilityVerbs, CAPABILITY_READ_VERBS } from './capability.mjs';
20
+ import { createCapabilityVerbs, CAPABILITY_READ_VERBS, hubOf } from './capability.mjs';
21
21
  import { createSweepVerbs, SWEEP_WRITE_VERBS, SWEEP_READ_VERBS } from './sweep.mjs';
22
22
 
23
23
  export { VerbError, canonicalJson, sha256, COSTS, LIVE_LIFECYCLES, REPO_KINDS, LIVE_KINDS, repoKinds };
@@ -237,9 +237,9 @@ export function createVerbs({ client, store, writer, session, agent = 'lead', bo
237
237
 
238
238
  async registry_list() {
239
239
  const reg = await registry();
240
- const products = [...reg.products.values()].map((row) => ({ registry_id: row.fields.registry_id, name: row.fields.name, entity_id: row.id, project_id: row.fields.project_id ?? null, notes: row.fields.notes ?? '',
240
+ const products = [...reg.products.values()].map((row) => ({ registry_id: row.fields.registry_id, name: row.fields.name, entity_id: row.id, project_id: row.fields.project_id ?? null, notes: row.fields.notes ?? '', design_hub_url: row.fields.design_hub_url ?? null,
241
241
  repos: [...reg.repos.values()].filter((repo) => repo.fields.product === row.fields.registry_id).map((repo) => ({ registry_id: repo.fields.registry_id, entity_id: repo.id, kind: repo.fields.kind ?? null, kinds: repoKinds(repo.fields), default_branch: repo.fields.default_branch, notes: repo.fields.notes ?? '' })) }));
242
- return { verb: 'registry_list', products, read_at: now() };
242
+ return { verb: 'registry_list', products, ...hubOf(reg), read_at: now() };
243
243
  },
244
244
 
245
245
  async product_add(args, requestId, meta) {
@@ -278,6 +278,12 @@ export function createVerbs({ client, store, writer, session, agent = 'lead', bo
278
278
  if (args.notes !== undefined) fields.notes = text(args.notes, 'notes', { max: 4000 });
279
279
  if (args.health_url !== undefined) fields.health_url = url(args.health_url, 'health_url');
280
280
  if (args.check_paths !== undefined) fields.check_paths = absolutePaths(args.check_paths);
281
+ // The one design hub page for the tenant. registry_list returns it, so
282
+ // a new session republishes the same page instead of a second one.
283
+ if (args.design_hub_url !== undefined) {
284
+ fields.design_hub_url = url(args.design_hub_url, 'design_hub_url');
285
+ if (!fields.design_hub_url.startsWith('https://')) refuse('design_hub_url must be an https URL');
286
+ }
281
287
  if (args.project_id !== undefined) {
282
288
  let project;
283
289
  try { project = await client.getEntity(text(args.project_id, 'project_id', { max: 200 })); } catch (error) { if (error instanceof EngramHttpError && error.status === 404) refuse(`unknown project entity: ${args.project_id}`); throw error; }
@@ -286,7 +292,7 @@ export function createVerbs({ client, store, writer, session, agent = 'lead', bo
286
292
  await edge(record, project, 'references', requestId);
287
293
  }
288
294
  if (alreadyPatched(record, requestId, meta)) return { verb: 'product_edit', registry_id: registryId, entity_id: record.id, version: record.version, changed: [], replayed_from_graph: true };
289
- if (!Object.keys(fields).length) refuse('product edit needs at least one of name, notes, health_url, check_paths, project_id; registry_id is immutable');
295
+ if (!Object.keys(fields).length) refuse('product edit needs at least one of name, notes, health_url, check_paths, design_hub_url, project_id; registry_id is immutable');
290
296
  const entity = await patch(record, 'product_edit', requestId, fields, { title: fields.name, digest: meta?.digest });
291
297
  return { verb: 'product_edit', registry_id: registryId, entity_id: entity.id, version: entity.version, changed: Object.keys(fields) };
292
298
  },
package/work/mcp.mjs CHANGED
@@ -39,7 +39,7 @@ export const TOOLS = [
39
39
  { name: 'registry_list', description: `List the registered products and their repos with registry ids and entity ids. When: before any item verb, to find the ids to pass. Scope: read, no owner flag needed. ${RULES}`, inputSchema: { type: 'object', properties: {}, additionalProperties: false } },
40
40
  { name: 'setup', description: `Publish atlas-work ${MANIFEST_VERSION} to the tenant's Forge and register its types (product, repo with its kinds, container, decision, question, receipt, mark and the surviving edges). Idempotent. When: once per tenant, before the first product add, and again after a manifest change. Owner present only. ${RULES}`, inputSchema: { type: 'object', required: ['request_id'], properties: { request_id: REQUEST_ID }, additionalProperties: false } },
41
41
  { name: 'product_add', description: `Register a product in the name registry. Refuses when the id exists. When: a product gets its first work item. Owner present only. registry_id is a lowercase slug and is immutable. ${RULES}`, inputSchema: { type: 'object', required: ['request_id', 'registry_id', 'name'], properties: { request_id: REQUEST_ID, registry_id: S('Lowercase slug, for example "engram". Immutable.'), name: S('Display name.'), project_id: S('Entity id of the existing Engram project entity this product links to.'), health_url: S('Absolute https URL of the deployed app\'s health endpoint. The done verb reads the deployed commit there, and every live check must be on this origin.'), check_paths: { type: 'array', items: { type: 'string' }, description: 'Absolute paths on that origin the functional check of item_done may read, for example ["/v1/housekeeping"]. Unset, the check may read only the health endpoint\'s own path.' }, notes: S('Free text.') }, additionalProperties: false } },
42
- { name: 'product_edit', description: `Change a product's name, notes, health endpoint, check paths or project link. Refuses an unknown id. The registry id never changes. Owner present only. ${RULES}`, inputSchema: { type: 'object', required: ['request_id', 'registry_id'], properties: { request_id: REQUEST_ID, registry_id: S('Existing product id.'), name: S('New display name.'), project_id: S('Entity id of an Engram project entity.'), health_url: S('Absolute https URL of the deployed app\'s health endpoint.'), check_paths: { type: 'array', items: { type: 'string' }, description: 'Absolute paths the functional check of item_done may read on that origin.' }, notes: S('Free text.') }, additionalProperties: false } },
42
+ { name: 'product_edit', description: `Change a product's name, notes, health endpoint, check paths, design hub link or project link. Refuses an unknown id. The registry id never changes. Owner present only. ${RULES}`, inputSchema: { type: 'object', required: ['request_id', 'registry_id'], properties: { request_id: REQUEST_ID, registry_id: S('Existing product id.'), name: S('New display name.'), project_id: S('Entity id of an Engram project entity.'), health_url: S('Absolute https URL of the deployed app\'s health endpoint.'), check_paths: { type: 'array', items: { type: 'string' }, description: 'Absolute paths the functional check of item_done may read on that origin.' }, design_hub_url: S('Absolute https URL of the one pinned "Design docs" hub page. Set it once, on any product; registry_list and design_index return it.'), notes: S('Free text.') }, additionalProperties: false } },
43
43
  { name: 'repo_add', description: `Register a GitHub repo under a product. The server reads GitHub itself: the id must equal the repo's full name and the owner must have push rights, or the add is refused. The default branch comes from GitHub. The kinds say what the repo is; item_done reads them to choose how a delivery is proved. Give kinds, a list; kind, one value, is the older form. Refuses an unknown product id and an existing repo id; repo ids are immutable. Owner present only. ${RULES}`, inputSchema: { type: 'object', required: ['request_id', 'registry_id', 'product'], properties: { request_id: REQUEST_ID, registry_id: S('GitHub "owner/repo", exact spelling.'), product: S('Existing product id.'), kinds: KINDS, kind: S(`The older form: one kind. One of ${REPO_KINDS.join(', ')}. Give kinds instead.`), notes: S('Free text.') }, additionalProperties: false } },
44
44
  { name: 'repo_edit', description: `Change a repo's kinds or notes, or refresh its default branch from GitHub. registry_id and product never change. Owner present only. ${RULES}`, inputSchema: { type: 'object', required: ['request_id', 'registry_id'], properties: { request_id: REQUEST_ID, registry_id: S('Existing repo id.'), kinds: KINDS, kind: S(`The older form: one kind. One of ${REPO_KINDS.join(', ')}. Give kinds instead.`), notes: S('Free text.'), refresh_default_branch: { type: 'boolean', description: 'Re-read the default branch and push rights from GitHub.' } }, additionalProperties: false } },
45
45
  { name: 'item_propose', description: `Create a proposed work item (lifecycle proposed, stage capture) for a product, with its repos and the owner's words as origin. When: the owner names a piece of work that is not yet locked. Owner present only. ${RULES}`, inputSchema: { type: 'object', required: ['request_id', 'title', 'product', 'repos', 'origin'], properties: { request_id: REQUEST_ID, title: S('Short title in the owner\'s words.'), product: S('Product id.'), repos: { type: 'array', items: { type: 'string' }, description: 'Repo ids under that product.' }, origin: ORIGIN, tier: S(`One of ${TIERS.join(', ')}. Default standard.`), driver: S(`One of ${DRIVERS.join(', ')}. Default owner-led.`), next_step: S('One line: what happens next.'), summary: S('Short summary.'), body: S('Longer body, optional.'), tags: { type: 'array', items: { type: 'string' } } }, additionalProperties: false } },
@@ -64,6 +64,7 @@ export const TOOLS = [
64
64
  { name: 'code_read_brief', description: 'The brief for the sub-agents that read the code for "where are we with X", one per registered repo of the product. It carries the never-read and never-quote rules, the citation contract and the JSON shape of the digest. Give one brief to one sub-agent; sub-agents never receive this server or the Engram MCP. Pass each digest back in where_are_we.code. When: before answering "where are we with X". Scope: read, no owner flag needed; it opens no repo and writes nothing.', inputSchema: { type: 'object', required: ['product', 'capability'], properties: { product: S('Product registry id.'), capability: S('The capability to read for, the X of the question.'), repos: { type: 'array', description: 'Limit the briefs to these registered repo ids. Default: every repo of the product.', items: { type: 'string' } }, roots: { type: 'object', description: 'Repo id to the local checkout path the sub-agent reads.', additionalProperties: { type: 'string' } }, commits: { type: 'object', description: 'Repo id to the commit the read runs against.', additionalProperties: { type: 'string' } }, focus: { type: 'array', description: 'Extra things the read should look for.', items: { type: 'string' } } }, additionalProperties: false } },
65
65
  { name: 'where_are_we', description: 'Answer "where are we with X" in six parts: live support and known limits from the code read at question time, then in progress, delivered and proposed from the items, then the current priority decision. Every row carries a citation: a path and a line, an entity id, a pull request, or a decision date. The code half comes from the sub-agent digests, which are refused when they cite a never-read path or quote a never-quote path, and every string is scrubbed by pattern. Computed at read time and never stored. Scope: read, no owner flag needed.', inputSchema: { type: 'object', required: ['product', 'capability', 'code'], properties: { product: S('Product registry id.'), capability: S('The capability the question is about, the X.'), code: { type: 'array', description: 'One digest per repo, from the sub-agents briefed by code_read_brief. At least one is required: an answer with no code read says nothing about what the code does today.', items: CODE_DIGEST }, items: { type: 'array', description: 'Item ids the lead judges to answer this question.', items: { type: 'string' } }, match: { type: 'array', description: 'Terms to match against item titles, summaries and tags. Give items, match, or both.', items: { type: 'string' } }, movement_repos: { type: 'array', description: "Narrow the movement read to these registered repo ids. Default: every repo of the product. The digests' own repos are always read.", items: { type: 'string' } }, since: S('ISO date. Adds what changed since then.') }, additionalProperties: false } },
66
66
  { name: 'built_pending_possible', description: 'Answer "what are we building, what is built, what is pending and what can be done": delivered items by date, active items, paused items with their reason, proposed items with their age, and the decisions that set the order. Item records, delivery marks and decisions only; no code read and no GitHub read. Computed at read time and never stored. Scope: read, no owner flag needed.', inputSchema: { type: 'object', properties: { product: S('Limit the view to one product id.'), since: S('ISO date. Limits built and the decisions to what came after it.') }, additionalProperties: false } },
67
+ { name: 'design_index', description: 'List every design: each work item with a design-doc link, in any lifecycle, newest change first, with its product, its state for the hub ("Waiting on the owner", "Building", "Paused", "Shipped", "Dropped"), its design docs and its pages. Also returns design_hub_url, the one hub page. When: before the lead rebuilds the "Design docs" hub, after each design publish, lock and done. Item records only; computed at read time and never stored. Scope: read, no owner flag needed.', inputSchema: { type: 'object', properties: { product: S('Limit the list to one product id.') }, additionalProperties: false } },
67
68
  ];
68
69
 
69
70
  // The Engram connection: the address and the key, in one setting, separated by