@ignite-agent/agent 0.5.1 → 0.6.0

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.
@@ -0,0 +1,173 @@
1
+ import { z } from 'zod';
2
+ export const ENTITY_TYPES = ['company', 'product', 'person', 'concept', 'location', 'offering'];
3
+ const slug = z.string().regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, 'slug must be kebab-case: a-z, 0-9 and single dashes');
4
+ const isoDate = z.string().regex(/^\d{4}-\d{2}-\d{2}$/, 'date must be YYYY-MM-DD');
5
+ const httpUrl = z.string().regex(/^https?:\/\/\S+$/, 'url must be absolute http(s)');
6
+ const text = z.string().trim().min(1);
7
+ const definition = z
8
+ .object({
9
+ lede: z.string().trim().optional(),
10
+ category: z.string().trim().optional(),
11
+ founded: z.string().trim().optional(),
12
+ function: z.string().trim().optional(),
13
+ differentiator: z.string().trim().optional(),
14
+ })
15
+ .superRefine((value, ctx) => {
16
+ const hasLede = (value.lede ?? '') !== '';
17
+ const hasFields = (value.category ?? '') !== '' && (value.function ?? '') !== '';
18
+ if (!hasLede && !hasFields) {
19
+ ctx.addIssue({
20
+ code: 'custom',
21
+ message: 'definition needs a non-empty lede, or both category and function',
22
+ });
23
+ }
24
+ });
25
+ export const wikiEntitySchema = z.object({
26
+ entity_type: z.enum(ENTITY_TYPES),
27
+ canonical_name: text,
28
+ slug,
29
+ aliases: z.array(text).default([]),
30
+ definition,
31
+ sections: z
32
+ .array(z.object({
33
+ heading: text,
34
+ paragraphs: z.array(text).default([]),
35
+ stats: z
36
+ .array(z.object({
37
+ text,
38
+ citation: z.number().int().min(1),
39
+ }))
40
+ .default([]),
41
+ }))
42
+ .default([]),
43
+ cross_links: z.array(z.object({ target_slug: slug, anchor: z.string().optional() })).default([]),
44
+ citations: z
45
+ .array(z.object({
46
+ n: z.number().int().min(1),
47
+ url: httpUrl,
48
+ title: text,
49
+ accessed: isoDate,
50
+ }))
51
+ .default([]),
52
+ author: z.object({ name: text, url: httpUrl.optional() }).optional(),
53
+ changelog: z.array(z.object({ date: isoDate, note: text })).default([]),
54
+ same_as: z.array(httpUrl).default([]),
55
+ // Present on files `ignite wiki pull` wrote. A file without it was authored by a person and is never overwritten.
56
+ imported_from: z
57
+ .object({
58
+ source: z.literal('ignite'),
59
+ slug: z.string(),
60
+ dateModified: z.string(),
61
+ })
62
+ .optional(),
63
+ });
64
+ // Parses one file's JSON against the schema. Returns issues instead of throwing so a validator can list every file.
65
+ export function parseWikiEntity(file, raw) {
66
+ const result = wikiEntitySchema.safeParse(raw);
67
+ if (!result.success) {
68
+ return {
69
+ entity: null,
70
+ issues: result.error.issues.map((issue) => ({
71
+ file,
72
+ message: `${issue.path.length > 0 ? `${issue.path.join('.')}: ` : ''}${issue.message}`,
73
+ })),
74
+ };
75
+ }
76
+ return { entity: result.data, issues: [] };
77
+ }
78
+ // The cross-file invariants: everything a single-file schema cannot see.
79
+ export function validateWikiSet(entries) {
80
+ const issues = [];
81
+ const bySlug = new Map();
82
+ for (const { file, entity } of entries) {
83
+ const expected = fileSlug(file);
84
+ if (expected !== null && expected !== entity.slug) {
85
+ issues.push({ file, message: `slug "${entity.slug}" does not match the file name "${expected}"` });
86
+ }
87
+ const seenIn = bySlug.get(entity.slug);
88
+ if (seenIn) {
89
+ issues.push({ file, message: `duplicate slug "${entity.slug}" (also in ${seenIn})` });
90
+ }
91
+ else {
92
+ bySlug.set(entity.slug, file);
93
+ }
94
+ const citationIndices = new Set();
95
+ for (const citation of entity.citations) {
96
+ if (citationIndices.has(citation.n)) {
97
+ issues.push({ file, message: `duplicate citation index [${citation.n}]` });
98
+ }
99
+ citationIndices.add(citation.n);
100
+ }
101
+ entity.sections.forEach((section, s) => {
102
+ section.stats.forEach((stat, i) => {
103
+ if (!citationIndices.has(stat.citation)) {
104
+ issues.push({
105
+ file,
106
+ message: `sections[${s}].stats[${i}] cites [${stat.citation}] but no citation has n=${stat.citation}`,
107
+ });
108
+ }
109
+ });
110
+ });
111
+ }
112
+ for (const { file, entity } of entries) {
113
+ for (const link of entity.cross_links) {
114
+ if (!bySlug.has(link.target_slug)) {
115
+ issues.push({ file, message: `cross_links target "${link.target_slug}" resolves to no entity file` });
116
+ }
117
+ }
118
+ }
119
+ return issues;
120
+ }
121
+ export function fileSlug(file) {
122
+ const base = file.split(/[\\/]/).pop() ?? '';
123
+ if (!base.endsWith('.json'))
124
+ return null;
125
+ const stem = base.slice(0, -'.json'.length);
126
+ return stem.startsWith('_') ? null : stem;
127
+ }
128
+ export function entityTypeLabel(type) {
129
+ const labels = {
130
+ company: 'Company',
131
+ product: 'Product',
132
+ person: 'Person',
133
+ concept: 'Concept',
134
+ location: 'Location',
135
+ offering: 'Service',
136
+ };
137
+ return labels[type];
138
+ }
139
+ export function schemaOrgType(type) {
140
+ const map = {
141
+ company: 'Organization',
142
+ product: 'Product',
143
+ person: 'Person',
144
+ concept: 'Thing',
145
+ location: 'Place',
146
+ offering: 'Service',
147
+ };
148
+ return map[type];
149
+ }
150
+ // The canonical definition block as one lede paragraph: the stored lede when there is one, otherwise the
151
+ // fields joined in the order the template prescribes. Nothing is invented for a missing field.
152
+ export function definitionLede(entity) {
153
+ const d = entity.definition;
154
+ if (d.lede && d.lede.trim() !== '')
155
+ return d.lede.trim();
156
+ const category = d.category?.trim() || entityTypeLabel(entity.entity_type).toLowerCase();
157
+ const parts = [`${entity.canonical_name} is a ${category}`];
158
+ if (d.founded?.trim())
159
+ parts.push(`founded in ${d.founded.trim()}`);
160
+ if (d.function?.trim())
161
+ parts.push(`that ${stripTrailingPeriod(d.function.trim())}`);
162
+ let lede = `${parts.join(' ')}.`;
163
+ if (d.differentiator?.trim())
164
+ lede += ` ${ensurePeriod(d.differentiator.trim())}`;
165
+ return lede;
166
+ }
167
+ function stripTrailingPeriod(value) {
168
+ return value.replace(/\.+$/, '');
169
+ }
170
+ function ensurePeriod(value) {
171
+ return /[.!?]$/.test(value) ? value : `${value}.`;
172
+ }
173
+ //# sourceMappingURL=schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/wiki-content/schema.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ignite-agent/agent",
3
- "version": "0.5.1",
4
- "description": "Ignite for Next.js: App Router SDK for robots, sitemap, JSON-LD, metadata, crawl logging, hosted wiki/blog pages, and a content-inventory seed. Server-only. The front end reads JSON; MCP is for coding agents, not the browser.",
3
+ "version": "0.6.0",
4
+ "description": "Ignite for Next.js: App Router SDK for robots, sitemap, JSON-LD, metadata, crawl logging, hosted wiki/blog pages, a content-inventory seed, and a validated Brand Wiki from repo JSON. Server-only. The front end reads JSON; MCP is for coding agents, not the browser.",
5
5
  "type": "module",
6
6
  "license": "SEE LICENSE IN LICENSE",
7
7
  "private": false,
@@ -17,6 +17,7 @@
17
17
  "files": [
18
18
  "dist",
19
19
  "onboarding",
20
+ "skills",
20
21
  "LICENSE",
21
22
  "AI.txt",
22
23
  "dist/ignite.css",
@@ -34,16 +35,18 @@
34
35
  "./wiki": "./dist/operator/wiki.js",
35
36
  "./blog": "./dist/operator/blog.js",
36
37
  "./client": "./dist/client-public.js",
38
+ "./wiki-content": "./dist/wiki-content/index.js",
37
39
  "./styles.css": "./dist/ignite.css"
38
40
  },
39
41
  "scripts": {
40
42
  "build": "tsc -p tsconfig.json && node scripts/write-css.mjs",
41
43
  "prepublishOnly": "npm run build",
42
44
  "typecheck": "tsc -p tsconfig.json --noEmit",
43
- "test": "npm run build && node --test dist/client.test.js dist/mcp.test.js dist/operator/ui.test.js dist/operator/sitemap-merge.test.js dist/cli/project.test.js dist/cli/seed.test.js"
45
+ "test": "npm run build && node --test dist/client.test.js dist/mcp.test.js dist/operator/ui.test.js dist/operator/sitemap-merge.test.js dist/cli/project.test.js dist/cli/seed.test.js dist/wiki-content/wiki.test.js"
44
46
  },
45
47
  "dependencies": {
46
- "server-only": "^0.0.1"
48
+ "server-only": "^0.0.1",
49
+ "zod": "^4.5.4"
47
50
  },
48
51
  "peerDependencies": {
49
52
  "next": ">=15.0.0 <17.0.0",
@@ -57,6 +60,7 @@
57
60
  "devDependencies": {
58
61
  "@types/node": "^22.0.0",
59
62
  "@types/react": "^19.0.0",
63
+ "@types/react-dom": "^19.2.7",
60
64
  "next": "^16.3.0",
61
65
  "react": "^19.0.0",
62
66
  "typescript": "^5.6.0"
@@ -0,0 +1,130 @@
1
+ ---
2
+ name: ignite-brand-wiki
3
+ description: Build and maintain the Brand Wiki in a Next.js App Router site with @ignite-agent/agent — entity files in content/wiki, six fixed page regions, a build-time honesty gate, factual JSON-LD. Use when asked to add, generate, pull, validate or edit the wiki, or when touching content/wiki/*.json or app/wiki/*.
4
+ ---
5
+
6
+ # Brand Wiki on Next.js
7
+
8
+ One entity per page. Content lives in the repo as validated JSON under `content/wiki/`. Git is the CMS: a
9
+ page changes through a reviewable diff, never through an admin UI. Pages render statically; nothing in the
10
+ content path runs in the browser.
11
+
12
+ The structure is evidence-based. It is not a ranking lever, and JSON-LD here is factual structure only.
13
+ Never describe it to the user as a way to get cited or ranked.
14
+
15
+ ## Before you start
16
+
17
+ - App Router project (`app/` or `src/app/`), `@ignite-agent/agent` >= 0.6 installed.
18
+ - `IGNITE_SITE_ID`, `IGNITE_SITE_TOKEN` and `IGNITE_SITE_URL` set in `.env.local` and in the host. Run
19
+ `npx ignite doctor` if unsure. The token stays on the server. Never `NEXT_PUBLIC_`.
20
+ - Optional: `IGNITE_SITE_NAME` (title suffix), `IGNITE_WIKI_BASE_PATH` (default `/wiki`), `IGNITE_LOCALE`
21
+ (must match `<html lang>`).
22
+
23
+ ## 1. Pull the pages Ignite already wrote
24
+
25
+ ```bash
26
+ npx ignite wiki pull --dry-run # lists what would be written
27
+ npx ignite wiki pull
28
+ ```
29
+
30
+ Each published wiki page in the user's Ignite workspace becomes `content/wiki/<slug>.json`. The import
31
+ carries only what the page states: the lede, the sections, the paragraphs that cite a source, the source
32
+ list, the author, the dates. It starts every file as `entity_type: "concept"` with no aliases, and it
33
+ records `imported_from`. A file without `imported_from` was written by a person and is never overwritten.
34
+
35
+ ## 2. Review every imported file with the user
36
+
37
+ For each file, settle with the user (do not guess):
38
+
39
+ - `entity_type`: `company | product | person | concept | location | offering`.
40
+ - `canonical_name` and `aliases`. Every anchor on the site uses the canonical form; an alias is never an anchor.
41
+ - `definition`: either a `lede` paragraph, or `category` + `function` (+ optional `founded`,
42
+ `differentiator`). The lede is the first crawlable paragraph. Leave a field out rather than invent it.
43
+ - `cross_links`: `target_slug` of related entities. The anchor text rendered is the target's canonical name.
44
+ - `same_as`: only real, public profile URLs the user confirms.
45
+
46
+ A stat without a real source is deleted, not sourced from memory. A citation needs `n`, an http(s) `url`, a
47
+ `title` and an `accessed` date (`YYYY-MM-DD`). Dates and founders are facts the user supplies, never
48
+ inferred.
49
+
50
+ ## 3. Write new entities
51
+
52
+ Same shape, by hand or with the user. Minimum viable file:
53
+
54
+ ```json
55
+ {
56
+ "entity_type": "company",
57
+ "canonical_name": "Northwind",
58
+ "slug": "northwind",
59
+ "aliases": ["Northwind Traders"],
60
+ "definition": { "category": "logistics software", "founded": "2019", "function": "routes regional freight for small carriers", "differentiator": "Runs on the carrier's own data; nothing is pooled across customers." },
61
+ "sections": [
62
+ { "heading": "What does Northwind do?", "paragraphs": ["…"], "stats": [{ "text": "Carriers on Northwind cut empty miles by 12% in 2025 [1].", "citation": 1 }] }
63
+ ],
64
+ "cross_links": [{ "target_slug": "route-planning" }],
65
+ "citations": [{ "n": 1, "url": "https://northwind.example/reports/2025", "title": "Northwind 2025 operations report", "accessed": "2026-09-01" }],
66
+ "author": { "name": "Northwind editorial", "url": "https://northwind.example/about" },
67
+ "changelog": [{ "date": "2026-09-01", "note": "First published." }],
68
+ "same_as": ["https://www.linkedin.com/company/northwind"]
69
+ }
70
+ ```
71
+
72
+ Section headings are question-shaped. `[n]` markers in text link to `#cite-n`.
73
+
74
+ ## 4. Wire the routes (greenfield only; never overwrite a host file)
75
+
76
+ ```ts
77
+ // app/wiki/[slug]/page.tsx
78
+ export { default, generateStaticParams, generateMetadata, dynamicParams, revalidate } from '@ignite-agent/agent/wiki-content'
79
+ ```
80
+
81
+ ```ts
82
+ // app/wiki/page.tsx
83
+ export { WikiContentIndex as default, revalidate } from '@ignite-agent/agent/wiki-content'
84
+ ```
85
+
86
+ ```tsx
87
+ // app/wiki/layout.tsx (optional sidebar)
88
+ import { WikiContentLayout } from '@ignite-agent/agent/wiki-content'
89
+ export default function Layout({ children }: { children: React.ReactNode }) {
90
+ return <WikiContentLayout>{children}</WikiContentLayout>
91
+ }
92
+ ```
93
+
94
+ Sitemap: merge `wikiSitemapEntries()` from `@ignite-agent/agent/wiki-content` into the existing
95
+ `app/sitemap.ts` (with `mergeSitemap` from `@ignite-agent/agent/sitemap` when the host already has one).
96
+ `lastModified` is the entity's last-reviewed date, the same value the footer and JSON-LD show.
97
+
98
+ If `app/w/` already serves Ignite wiki pages at runtime, keep it or remove it with the user; do not run both
99
+ for the same slugs.
100
+
101
+ ## 5. Gate the build
102
+
103
+ ```json
104
+ { "scripts": { "prebuild": "ignite wiki validate" } }
105
+ ```
106
+
107
+ `ignite wiki validate` fails (exit 1, one `BUILD ERROR` line per problem) on: a stat citing an index no
108
+ citation has, a citation URL that is not http(s), a `cross_links` target with no file, duplicate slugs or
109
+ citation indices, a slug that does not match its file name, an empty definition. The page loader applies the
110
+ same schema and throws on drift, so `next build` cannot ship a page the gate refused.
111
+
112
+ ## 6. Prove it in the static HTML
113
+
114
+ After `next build`, for one entity:
115
+
116
+ ```bash
117
+ curl -s https://<site>/wiki/<slug> | grep -c 'id="definition"' # 1
118
+ curl -s https://<site>/wiki/<slug> | grep -c 'id="cite-1"' # 1 when the page has a citation
119
+ ```
120
+
121
+ The definition and citations must be in the raw response, not hydrated in. One `<h1>`, no heading skips, one
122
+ `application/ld+json` script per page.
123
+
124
+ ## Rules
125
+
126
+ - Server-only. No `"use client"` in the content path. No client fetching.
127
+ - Never invent a stat, a citation, a date, a founder or a `sameAs` URL. Missing means omitted.
128
+ - Never emit ratings, reviews, FAQPage or speakable JSON-LD; there is no data for them.
129
+ - Do not add a CMS, search, comments or revision UI. The PR diff is the accountability surface.
130
+ - Do not present any of this as a ranking lever.