atris 3.1.0 → 3.2.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.
package/commands/wiki.js CHANGED
@@ -5,6 +5,8 @@ const { apiRequestJson } = require('../utils/api');
5
5
  const { loadBusinesses, saveBusinesses } = require('./business');
6
6
  const {
7
7
  WIKI_ROOT,
8
+ PRIVATE_WIKI_ROOT,
9
+ getWikiRoot,
8
10
  ensureWikiScaffold,
9
11
  findLocalWikiDir,
10
12
  buildIngestPrompt,
@@ -31,9 +33,14 @@ function parseCloudArgs(args) {
31
33
 
32
34
  function parseModeArgs(args) {
33
35
  const cloud = args.includes('--cloud');
36
+ const privateMode = args.includes('--private');
37
+ if (cloud && privateMode) {
38
+ console.error('Use either --cloud or --private, not both.');
39
+ process.exit(1);
40
+ }
34
41
  return {
35
- mode: cloud ? 'cloud' : 'local',
36
- args: args.filter((arg) => arg !== '--cloud' && arg !== '--local'),
42
+ mode: cloud ? 'cloud' : (privateMode ? 'private' : 'local'),
43
+ args: args.filter((arg) => arg !== '--cloud' && arg !== '--local' && arg !== '--private'),
37
44
  };
38
45
  }
39
46
 
@@ -142,10 +149,10 @@ async function runChat(business, prompt, token) {
142
149
  }
143
150
  }
144
151
 
145
- function printLocalPrompt(title, prompt, details = []) {
152
+ function printLocalPrompt(title, prompt, wikiRoot, details = []) {
146
153
  console.log('');
147
154
  console.log(title);
148
- console.log(`Target: ${WIKI_ROOT}`);
155
+ console.log(`Target: ${wikiRoot}`);
149
156
  details.forEach((detail) => console.log(detail));
150
157
  console.log('');
151
158
  console.log('Prompt for the current coding agent:');
@@ -160,9 +167,10 @@ async function wikiIngest(mode, slug, sourceValue) {
160
167
  process.exit(1);
161
168
  }
162
169
 
163
- if (mode === 'local') {
164
- const wikiDir = ensureWikiScaffold();
165
- printLocalPrompt('Local wiki ingest', buildIngestPrompt(sourceValue), [
170
+ if (mode === 'local' || mode === 'private') {
171
+ const wikiMode = mode === 'private' ? 'private' : 'public';
172
+ const wikiDir = ensureWikiScaffold(process.cwd(), wikiMode);
173
+ printLocalPrompt(mode === 'private' ? 'Private wiki ingest' : 'Local wiki ingest', buildIngestPrompt(sourceValue, wikiMode), getWikiRoot(wikiMode), [
166
174
  `Wiki dir: ${wikiDir}`,
167
175
  `Sources: ${sourceValue}`,
168
176
  ]);
@@ -182,13 +190,14 @@ async function wikiQuery(mode, slug, question) {
182
190
  process.exit(1);
183
191
  }
184
192
 
185
- if (mode === 'local') {
186
- const wikiDir = findLocalWikiDir(process.cwd(), slug);
193
+ if (mode !== 'cloud') {
194
+ const wikiMode = mode === 'private' ? 'private' : 'public';
195
+ const wikiDir = findLocalWikiDir(process.cwd(), slug, wikiMode);
187
196
  if (!wikiDir) {
188
- console.error('No local atris/wiki found. Run: atris ingest <path>');
197
+ console.error(`No local wiki found at ${getWikiRoot(wikiMode)}. Run: atris wiki ingest${wikiMode === 'private' ? ' --private' : ''} <path>`);
189
198
  process.exit(1);
190
199
  }
191
- printLocalPrompt('Local wiki query', buildQueryPrompt(question), [
200
+ printLocalPrompt(mode === 'private' ? 'Private wiki query' : 'Local wiki query', buildQueryPrompt(question, wikiMode), getWikiRoot(wikiMode), [
192
201
  `Wiki dir: ${wikiDir}`,
193
202
  `Question: ${question}`,
194
203
  ]);
@@ -201,13 +210,14 @@ async function wikiQuery(mode, slug, question) {
201
210
  }
202
211
 
203
212
  async function wikiLint(mode, slug) {
204
- if (mode === 'local') {
205
- const wikiDir = findLocalWikiDir(process.cwd(), slug);
213
+ if (mode !== 'cloud') {
214
+ const wikiMode = mode === 'private' ? 'private' : 'public';
215
+ const wikiDir = findLocalWikiDir(process.cwd(), slug, wikiMode);
206
216
  if (!wikiDir) {
207
- console.error('No local atris/wiki found. Run: atris ingest <path>');
217
+ console.error(`No local wiki found at ${getWikiRoot(wikiMode)}. Run: atris wiki ingest${wikiMode === 'private' ? ' --private' : ''} <path>`);
208
218
  process.exit(1);
209
219
  }
210
- printLocalPrompt('Local wiki lint', buildLintPrompt(), [`Wiki dir: ${wikiDir}`]);
220
+ printLocalPrompt(mode === 'private' ? 'Private wiki lint' : 'Local wiki lint', buildLintPrompt(wikiMode), getWikiRoot(wikiMode), [`Wiki dir: ${wikiDir}`]);
211
221
  return;
212
222
  }
213
223
 
@@ -217,15 +227,16 @@ async function wikiLint(mode, slug) {
217
227
  await runChat(business, buildLintPrompt(), creds.token);
218
228
  }
219
229
 
220
- function wikiSearch(slug, query) {
230
+ function wikiSearch(mode, slug, query) {
221
231
  if (!query) {
222
232
  console.error('Usage: atris wiki search [business] <term>');
223
233
  process.exit(1);
224
234
  }
225
235
 
226
- const wikiDir = findLocalWikiDir(process.cwd(), slug);
236
+ const wikiMode = mode === 'private' ? 'private' : 'public';
237
+ const wikiDir = findLocalWikiDir(process.cwd(), slug, wikiMode);
227
238
  if (!wikiDir) {
228
- console.error(`No local wiki found. Run: atris pull ${slug || ''} --only wiki`);
239
+ console.error(`No local wiki found at ${getWikiRoot(wikiMode)}.`);
229
240
  process.exit(1);
230
241
  }
231
242
 
@@ -250,10 +261,11 @@ function wikiSearch(slug, query) {
250
261
  console.log('');
251
262
  }
252
263
 
253
- function wikiLog(slug, limit) {
254
- const wikiDir = findLocalWikiDir(process.cwd(), slug);
264
+ function wikiLog(mode, slug, limit) {
265
+ const wikiMode = mode === 'private' ? 'private' : 'public';
266
+ const wikiDir = findLocalWikiDir(process.cwd(), slug, wikiMode);
255
267
  if (!wikiDir) {
256
- console.error(`No local wiki found. Run: atris pull ${slug || ''} --only wiki`);
268
+ console.error(`No local wiki found at ${getWikiRoot(wikiMode)}.`);
257
269
  process.exit(1);
258
270
  }
259
271
 
@@ -314,14 +326,21 @@ async function wikiCommand(subcommand, ...args) {
314
326
  break;
315
327
  }
316
328
  case 'search': {
317
- const [slug, query] = parseCloudArgs(cleanArgs);
318
- wikiSearch(slug, query);
329
+ if (mode === 'private') {
330
+ wikiSearch(mode, null, cleanArgs.join(' '));
331
+ } else {
332
+ const [slug, query] = parseCloudArgs(cleanArgs);
333
+ wikiSearch(mode, slug, query);
334
+ }
319
335
  break;
320
336
  }
321
337
  case 'log': {
322
338
  let slug;
323
339
  let limit;
324
- if (cleanArgs.length === 0) {
340
+ if (mode === 'private') {
341
+ slug = null;
342
+ limit = parseInt(cleanArgs[0], 10) || 20;
343
+ } else if (cleanArgs.length === 0) {
325
344
  slug = autoDetectSlug();
326
345
  limit = 20;
327
346
  } else if (cleanArgs.length === 1) {
@@ -336,7 +355,7 @@ async function wikiCommand(subcommand, ...args) {
336
355
  slug = cleanArgs[0];
337
356
  limit = parseInt(cleanArgs[1], 10) || 20;
338
357
  }
339
- wikiLog(slug, limit);
358
+ wikiLog(mode, slug, limit);
340
359
  break;
341
360
  }
342
361
  case 'loop': {
@@ -361,6 +380,7 @@ async function wikiCommand(subcommand, ...args) {
361
380
  console.log('Flags:');
362
381
  console.log(' --cloud Route ingest/query/lint to the cloud workspace');
363
382
  console.log(' --local Be explicit about local mode');
383
+ console.log(` --private Use local private wiki at ${PRIVATE_WIKI_ROOT}/`);
364
384
  console.log('');
365
385
  console.log('Business is auto-detected from .atris/business.json for cloud mode if omitted.');
366
386
  }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Frozen reward constants for the RL loop.
3
+ *
4
+ * These live outside mutable repo state so the loop cannot edit its own
5
+ * judge. REWARD_CHECKSUM is the SHA-256 of JSON.stringify(REWARD_CONFIG)
6
+ * + computeTickReward.toString() at ship time — if the config values or
7
+ * function body change, verifyJudgeIntegrity() halts the next tick.
8
+ */
9
+
10
+ const crypto = require('crypto');
11
+
12
+ const REWARD_CONFIG = Object.freeze({
13
+ REVIEW_CLEAN: 1,
14
+ VERIFY_PASS: 3,
15
+ NPM_TEST_BONUS: 2,
16
+ COMMIT_LANDED: 1,
17
+ HALT_PENALTY: -3,
18
+ });
19
+
20
+ // SHA-256 of JSON.stringify(REWARD_CONFIG) + computeTickReward.toString() at ship time.
21
+ // Regenerate: node -e "const c=require('./lib/reward-config');const h=require('crypto').createHash('sha256');h.update(JSON.stringify(c.REWARD_CONFIG));h.update(require('./commands/autopilot').computeTickReward.toString());console.log(h.digest('hex'))"
22
+ const REWARD_CHECKSUM = '5a84be0f7f392d6ef05337be0776f864852e94d6391da0b41486298555595a40';
23
+
24
+ module.exports = { REWARD_CONFIG, REWARD_CHECKSUM };
package/lib/scorecard.js CHANGED
@@ -2,6 +2,18 @@ const fs = require('fs');
2
2
  const path = require('path');
3
3
  const { parseTodo } = require('./todo');
4
4
 
5
+ const PRIVATE_MEMORY_ROOT = '.atris/presidio';
6
+
7
+ function ensurePrivateMemoryDir(atrisDir) {
8
+ const privateDir = path.join(path.dirname(atrisDir), PRIVATE_MEMORY_ROOT);
9
+ fs.mkdirSync(privateDir, { recursive: true });
10
+ return privateDir;
11
+ }
12
+
13
+ function getScorecardsPath(atrisDir) {
14
+ return path.join(ensurePrivateMemoryDir(atrisDir), 'scorecards.md');
15
+ }
16
+
5
17
  function parsePickedAt(value) {
6
18
  if (!value) return null;
7
19
  const match = String(value).trim().match(/^(\d{4}-\d{2}-\d{2})(?:\s+(\d{2}:\d{2}))?/);
@@ -191,7 +203,7 @@ function writeScorecard(atrisDir, data) {
191
203
  throw new Error('Scorecard: slug is required');
192
204
  }
193
205
 
194
- const scorecardsPath = path.join(atrisDir, 'scorecards.md');
206
+ const scorecardsPath = getScorecardsPath(atrisDir);
195
207
 
196
208
  // Ensure scorecards.md exists
197
209
  if (!fs.existsSync(scorecardsPath)) {
@@ -244,7 +256,7 @@ function detectEndgameCompletion(atrisDir) {
244
256
  * Parse scorecards.md and return array of scorecard objects.
245
257
  */
246
258
  function readScorecards(atrisDir) {
247
- const scorecardsPath = path.join(atrisDir, 'scorecards.md');
259
+ const scorecardsPath = getScorecardsPath(atrisDir);
248
260
  if (!fs.existsSync(scorecardsPath)) return [];
249
261
 
250
262
  const content = fs.readFileSync(scorecardsPath, 'utf8');
@@ -280,6 +292,8 @@ function readScorecards(atrisDir) {
280
292
  }
281
293
 
282
294
  module.exports = {
295
+ PRIVATE_MEMORY_ROOT,
296
+ getScorecardsPath,
283
297
  buildScorecardData,
284
298
  writeScorecard,
285
299
  readScorecards,
package/lib/wiki.js CHANGED
@@ -2,6 +2,7 @@ const fs = require('fs');
2
2
  const path = require('path');
3
3
 
4
4
  const WIKI_ROOT = 'atris/wiki';
5
+ const PRIVATE_WIKI_ROOT = '.atris/presidio';
5
6
  const LEGACY_WIKI_ROOT = 'wiki';
6
7
  const WIKI_BRIEFS_SUBDIR = 'briefs';
7
8
  const LEGACY_WIKI_BRIEFS_SUBDIR = 'syntheses';
@@ -9,6 +10,14 @@ const WIKI_SUBDIRS = ['people', 'systems', 'concepts', WIKI_BRIEFS_SUBDIR];
9
10
  const WIKI_STATUS_FILE = 'STATUS.md';
10
11
  const WIKI_CONTENT_SUBDIRS = WIKI_SUBDIRS.map((subdir) => path.join(WIKI_ROOT, subdir));
11
12
 
13
+ function getWikiRoot(mode = 'public') {
14
+ return mode === 'private' ? PRIVATE_WIKI_ROOT : WIKI_ROOT;
15
+ }
16
+
17
+ function getWikiLinkRoot(mode = 'public') {
18
+ return mode === 'private' ? PRIVATE_WIKI_ROOT : 'atris/wiki';
19
+ }
20
+
12
21
  function today() {
13
22
  return new Date().toISOString().slice(0, 10);
14
23
  }
@@ -17,10 +26,12 @@ function nowTime() {
17
26
  return new Date().toTimeString().slice(0, 5);
18
27
  }
19
28
 
20
- function protocolMarkdown() {
29
+ function protocolMarkdown(mode = 'public') {
30
+ const wikiRoot = getWikiRoot(mode);
31
+ const wikiLinkRoot = getWikiLinkRoot(mode);
21
32
  return `# Atris Wiki Protocol
22
33
 
23
- This wiki lives in \`${WIKI_ROOT}/\`.
34
+ This wiki lives in \`${wikiRoot}/\`.
24
35
 
25
36
  ## Purpose
26
37
 
@@ -28,20 +39,20 @@ Turn raw project context into a living memory the next agent can pick up cold.
28
39
 
29
40
  ## Shape
30
41
 
31
- - \`${WIKI_ROOT}/wiki.md\` - this protocol
32
- - \`${WIKI_ROOT}/index.md\` - catalog grouped by page type
33
- - \`${WIKI_ROOT}/log.md\` - append-only ingest and lint history
34
- - \`${WIKI_ROOT}/STATUS.md\` - plain-English health summary
35
- - \`${WIKI_ROOT}/people/\` - humans (employees, contacts, stakeholders)
36
- - \`${WIKI_ROOT}/systems/\` - tools, tables, dashboards, services, products
37
- - \`${WIKI_ROOT}/concepts/\` - patterns, frameworks, recurring ideas
38
- - \`${WIKI_ROOT}/${WIKI_BRIEFS_SUBDIR}/\` - multi-page briefs and cross-cutting analysis
42
+ - \`${wikiRoot}/wiki.md\` - this protocol
43
+ - \`${wikiRoot}/index.md\` - catalog grouped by page type
44
+ - \`${wikiRoot}/log.md\` - append-only ingest and lint history
45
+ - \`${wikiRoot}/STATUS.md\` - plain-English health summary
46
+ - \`${wikiRoot}/people/\` - humans (employees, contacts, stakeholders)
47
+ - \`${wikiRoot}/systems/\` - tools, tables, dashboards, services, products
48
+ - \`${wikiRoot}/concepts/\` - patterns, frameworks, recurring ideas
49
+ - \`${wikiRoot}/${WIKI_BRIEFS_SUBDIR}/\` - multi-page briefs and cross-cutting analysis
39
50
 
40
51
  ## Rules
41
52
 
42
53
  - Read the full source before writing.
43
54
  - Merge new facts into existing pages. Do not overwrite history blindly.
44
- - Add cross-references with \`[[atris/wiki/...]]\` links.
55
+ - Add cross-references with \`[[${wikiLinkRoot}/...]]\` links.
45
56
  - Keep \`index.md\`, \`log.md\`, and \`STATUS.md\` in sync with page changes.
46
57
  - If something is unclear or contradictory, say so directly.
47
58
  `;
@@ -155,15 +166,18 @@ function migrateLegacyBriefsDir(wikiDir) {
155
166
  rewriteLegacyWikiReferences(wikiDir);
156
167
  }
157
168
 
158
- function ensureWikiScaffold(projectRoot = process.cwd()) {
159
- const wikiDir = path.join(projectRoot, WIKI_ROOT);
169
+ function ensureWikiScaffold(projectRoot = process.cwd(), mode = 'public') {
170
+ const wikiRoot = getWikiRoot(mode);
171
+ const wikiDir = path.join(projectRoot, wikiRoot);
160
172
  fs.mkdirSync(wikiDir, { recursive: true });
161
- migrateLegacyBriefsDir(wikiDir);
173
+ if (mode === 'public') {
174
+ migrateLegacyBriefsDir(wikiDir);
175
+ }
162
176
  for (const subdir of WIKI_SUBDIRS) {
163
177
  fs.mkdirSync(path.join(wikiDir, subdir), { recursive: true });
164
178
  }
165
179
 
166
- ensureFile(path.join(wikiDir, 'wiki.md'), protocolMarkdown());
180
+ ensureFile(path.join(wikiDir, 'wiki.md'), protocolMarkdown(mode));
167
181
  ensureFile(path.join(wikiDir, 'index.md'), indexMarkdown());
168
182
  ensureFile(path.join(wikiDir, 'log.md'), logMarkdown());
169
183
  ensureFile(path.join(wikiDir, WIKI_STATUS_FILE), statusMarkdown());
@@ -171,7 +185,12 @@ function ensureWikiScaffold(projectRoot = process.cwd()) {
171
185
  return wikiDir;
172
186
  }
173
187
 
174
- function findLocalWikiDir(projectRoot = process.cwd(), slug = null) {
188
+ function findLocalWikiDir(projectRoot = process.cwd(), slug = null, mode = 'public') {
189
+ if (mode === 'private') {
190
+ const privateDir = path.join(projectRoot, PRIVATE_WIKI_ROOT);
191
+ return fs.existsSync(privateDir) ? privateDir : null;
192
+ }
193
+
175
194
  const tries = [
176
195
  path.join(projectRoot, WIKI_ROOT),
177
196
  path.join(projectRoot, LEGACY_WIKI_ROOT),
@@ -190,8 +209,8 @@ function normalizeWikiOnlyPrefix(prefix) {
190
209
  return null;
191
210
  }
192
211
 
193
- function readWikiStatus(projectRoot = process.cwd(), slug = null) {
194
- const wikiDir = findLocalWikiDir(projectRoot, slug);
212
+ function readWikiStatus(projectRoot = process.cwd(), slug = null, mode = 'public') {
213
+ const wikiDir = findLocalWikiDir(projectRoot, slug, mode);
195
214
  if (!wikiDir) return null;
196
215
 
197
216
  const statusPath = path.join(wikiDir, WIKI_STATUS_FILE);
@@ -275,8 +294,9 @@ function parseFrontmatter(content) {
275
294
  return frontmatter;
276
295
  }
277
296
 
278
- function readWikiPages(projectRoot = process.cwd()) {
279
- const wikiDir = path.join(projectRoot, WIKI_ROOT);
297
+ function readWikiPages(projectRoot = process.cwd(), mode = 'public') {
298
+ const wikiRoot = getWikiRoot(mode);
299
+ const wikiDir = path.join(projectRoot, wikiRoot);
280
300
  const pages = [];
281
301
 
282
302
  for (const subdir of WIKI_SUBDIRS) {
@@ -302,8 +322,8 @@ function normalizeSourcePath(projectRoot, source) {
302
322
  return path.normalize(path.join(projectRoot, source));
303
323
  }
304
324
 
305
- function findStaleWikiPages(projectRoot = process.cwd()) {
306
- return readWikiPages(projectRoot)
325
+ function findStaleWikiPages(projectRoot = process.cwd(), mode = 'public') {
326
+ return readWikiPages(projectRoot, mode)
307
327
  .map((page) => {
308
328
  const sources = Array.isArray(page.frontmatter.sources) ? page.frontmatter.sources : [];
309
329
  if (sources.length === 0) return null;
@@ -345,13 +365,14 @@ function findStaleWikiPages(projectRoot = process.cwd()) {
345
365
  }
346
366
 
347
367
  function extractWikiLinks(content) {
348
- const matches = content.match(/\[\[(atris\/wiki\/[^\]]+?)\]\]/g) || [];
368
+ const matches = content.match(/\[\[((?:atris\/wiki|\.atris\/presidio)\/[^\]]+?)\]\]/g) || [];
349
369
  return matches.map((match) => match.slice(2, -2));
350
370
  }
351
371
 
352
- function findWikiOrphans(projectRoot = process.cwd()) {
353
- const pages = readWikiPages(projectRoot);
354
- const indexPath = path.join(projectRoot, WIKI_ROOT, 'index.md');
372
+ function findWikiOrphans(projectRoot = process.cwd(), mode = 'public') {
373
+ const wikiRoot = getWikiRoot(mode);
374
+ const pages = readWikiPages(projectRoot, mode);
375
+ const indexPath = path.join(projectRoot, wikiRoot, 'index.md');
355
376
  const indexContent = fs.existsSync(indexPath) ? fs.readFileSync(indexPath, 'utf8') : '';
356
377
 
357
378
  const inboundLinks = new Map();
@@ -423,8 +444,8 @@ function parseStatusBullets(content) {
423
444
  return bullets;
424
445
  }
425
446
 
426
- function writeWikiStatus(projectRoot = process.cwd(), report) {
427
- const wikiDir = ensureWikiScaffold(projectRoot);
447
+ function writeWikiStatus(projectRoot = process.cwd(), report, mode = 'public') {
448
+ const wikiDir = ensureWikiScaffold(projectRoot, mode);
428
449
  const statusPath = path.join(wikiDir, WIKI_STATUS_FILE);
429
450
  const existing = fs.existsSync(statusPath) ? fs.readFileSync(statusPath, 'utf8') : '';
430
451
  const bullets = parseStatusBullets(existing);
@@ -444,8 +465,8 @@ function writeWikiStatus(projectRoot = process.cwd(), report) {
444
465
  return statusPath;
445
466
  }
446
467
 
447
- function appendWikiLog(projectRoot = process.cwd(), summary, details = []) {
448
- const wikiDir = ensureWikiScaffold(projectRoot);
468
+ function appendWikiLog(projectRoot = process.cwd(), summary, details = [], mode = 'public') {
469
+ const wikiDir = ensureWikiScaffold(projectRoot, mode);
449
470
  const logPath = path.join(wikiDir, 'log.md');
450
471
  let content = fs.existsSync(logPath) ? fs.readFileSync(logPath, 'utf8') : '# Atris Wiki Log\n';
451
472
  const dateHeader = `## ${today()}`;
@@ -471,17 +492,20 @@ function formatSourceList(sourceValue) {
471
492
  .join(', ');
472
493
  }
473
494
 
474
- const WIKI_SCHEMA = `The wiki lives in ${WIKI_ROOT}/.
495
+ function buildWikiSchema(mode = 'public') {
496
+ const wikiRoot = getWikiRoot(mode);
497
+ const wikiLinkRoot = getWikiLinkRoot(mode);
498
+ return `The wiki lives in ${wikiRoot}/.
475
499
 
476
500
  Structure:
477
- - ${WIKI_ROOT}/wiki.md - protocol for future agents
478
- - ${WIKI_ROOT}/index.md - catalog grouped by type
479
- - ${WIKI_ROOT}/log.md - append-only activity log
480
- - ${WIKI_ROOT}/STATUS.md - plain-English health summary
481
- - ${WIKI_ROOT}/people/ - one page per human
482
- - ${WIKI_ROOT}/systems/ - one page per tool, table, dashboard, service, or product
483
- - ${WIKI_ROOT}/concepts/ - pattern and framework pages
484
- - ${WIKI_ROOT}/${WIKI_BRIEFS_SUBDIR}/ - cross-cutting briefs referencing 3+ pages
501
+ - ${wikiRoot}/wiki.md - protocol for future agents
502
+ - ${wikiRoot}/index.md - catalog grouped by type
503
+ - ${wikiRoot}/log.md - append-only activity log
504
+ - ${wikiRoot}/STATUS.md - plain-English health summary
505
+ - ${wikiRoot}/people/ - one page per human
506
+ - ${wikiRoot}/systems/ - one page per tool, table, dashboard, service, or product
507
+ - ${wikiRoot}/concepts/ - pattern and framework pages
508
+ - ${wikiRoot}/${WIKI_BRIEFS_SUBDIR}/ - cross-cutting briefs referencing 3+ pages
485
509
 
486
510
  Page format:
487
511
  ---
@@ -497,7 +521,7 @@ tags: [tag1, tag2]
497
521
  # Title
498
522
  Body in markdown.
499
523
  ## Cross-References
500
- - [[atris/wiki/people/related.md]] - why related
524
+ - [[${wikiLinkRoot}/people/related.md]] - why related
501
525
 
502
526
  Rules:
503
527
  - Read every listed source fully before writing
@@ -505,20 +529,23 @@ Rules:
505
529
  - Keep index.md, log.md, and STATUS.md current
506
530
  - Flag contradictions directly instead of smoothing them over
507
531
  - Never modify the raw source documents you ingested`;
532
+ }
508
533
 
509
- function buildIngestPrompt(sourceValue) {
534
+ function buildIngestPrompt(sourceValue, mode = 'public') {
535
+ const wikiRoot = getWikiRoot(mode);
536
+ const wikiLinkRoot = getWikiLinkRoot(mode);
510
537
  return `Atris wiki ingest: ${formatSourceList(sourceValue)}
511
- ${WIKI_SCHEMA}
538
+ ${buildWikiSchema(mode)}
512
539
 
513
540
  Workflow:
514
541
  1. Read every source in: ${sourceValue}
515
- 2. Ensure ${WIKI_ROOT}/ exists with wiki.md, index.md, log.md, STATUS.md, and the 3 page subfolders
542
+ 2. Ensure ${wikiRoot}/ exists with wiki.md, index.md, log.md, STATUS.md, and the 3 page subfolders
516
543
  3. Extract people, systems, and concepts worth preserving
517
- 4. Create or update pages under ${WIKI_ROOT}/, merging with existing facts instead of replacing them
518
- 5. Add cross-references using [[atris/wiki/...]] links
519
- 6. Update ${WIKI_ROOT}/index.md with one-line descriptions of touched pages
520
- 7. Append an INGEST entry to ${WIKI_ROOT}/log.md under today's date
521
- 8. Refresh ${WIKI_ROOT}/STATUS.md in plain English for a non-technical reader
544
+ 4. Create or update pages under ${wikiRoot}/, merging with existing facts instead of replacing them
545
+ 5. Add cross-references using [[${wikiLinkRoot}/...]] links
546
+ 6. Update ${wikiRoot}/index.md with one-line descriptions of touched pages
547
+ 7. Append an INGEST entry to ${wikiRoot}/log.md under today's date
548
+ 8. Refresh ${wikiRoot}/STATUS.md in plain English for a non-technical reader
522
549
 
523
550
  Quality bar:
524
551
  - Ask clarifying questions if the source is ambiguous
@@ -527,18 +554,20 @@ Quality bar:
527
554
  - Leave the wiki sharper than you found it`;
528
555
  }
529
556
 
530
- function buildQueryPrompt(question) {
557
+ function buildQueryPrompt(question, mode = 'public') {
558
+ const wikiRoot = getWikiRoot(mode);
531
559
  return `Atris wiki query: ${question}
532
560
 
533
- Read ${WIKI_ROOT}/index.md first, then the most relevant pages.
534
- Answer from the wiki with direct references to page paths under ${WIKI_ROOT}/.
561
+ Read ${wikiRoot}/index.md first, then the most relevant pages.
562
+ Answer from the wiki with direct references to page paths under ${wikiRoot}/.
535
563
  If the answer reveals a reusable insight, offer to save it as a brief page.`;
536
564
  }
537
565
 
538
- function buildLintPrompt() {
566
+ function buildLintPrompt(mode = 'public') {
567
+ const wikiRoot = getWikiRoot(mode);
539
568
  return `Atris wiki lint pass
540
569
 
541
- Read ${WIKI_ROOT}/index.md, crawl the referenced pages, and inspect the local wiki.
570
+ Read ${wikiRoot}/index.md, crawl the referenced pages, and inspect the local wiki.
542
571
 
543
572
  Checks:
544
573
  1. Every page referenced by index.md exists
@@ -546,8 +575,8 @@ Checks:
546
575
  3. Orphan pages are listed
547
576
  4. Contradictions are called out plainly
548
577
  5. Gaps worth ingesting next are listed concretely
549
- 6. ${WIKI_ROOT}/STATUS.md is rewritten in plain English
550
- 7. ${WIKI_ROOT}/log.md gets a LINT entry under today's date
578
+ 6. ${wikiRoot}/STATUS.md is rewritten in plain English
579
+ 7. ${wikiRoot}/log.md gets a LINT entry under today's date
551
580
 
552
581
  Output:
553
582
  - Clear summary for a non-technical reader
@@ -557,11 +586,13 @@ Output:
557
586
 
558
587
  module.exports = {
559
588
  WIKI_ROOT,
589
+ PRIVATE_WIKI_ROOT,
560
590
  LEGACY_WIKI_ROOT,
561
591
  WIKI_SUBDIRS,
562
592
  WIKI_CONTENT_SUBDIRS,
563
- WIKI_SCHEMA,
593
+ WIKI_SCHEMA: buildWikiSchema(),
564
594
  WIKI_STATUS_FILE,
595
+ getWikiRoot,
565
596
  ensureWikiScaffold,
566
597
  findLocalWikiDir,
567
598
  normalizeWikiOnlyPrefix,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "atris",
3
- "version": "3.1.0",
3
+ "version": "3.2.0",
4
4
  "description": "Atris — an operating system for intelligence. Integrates with any agent.",
5
5
  "main": "bin/atris.js",
6
6
  "bin": {
@@ -32,7 +32,8 @@
32
32
  "atris/skills/"
33
33
  ],
34
34
  "scripts": {
35
- "test": "node --test"
35
+ "test": "node --test",
36
+ "prepare": "cp scripts/pre-commit .git/hooks/pre-commit 2>/dev/null && chmod +x .git/hooks/pre-commit || true"
36
37
  },
37
38
  "keywords": [
38
39
  "atrisdev",