Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion integration/tooling.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Transitrix separates validation into two canonical axes (file scope and repo sco
|---|---|---|---|---|
| `npx @transitrix/cli validate <file>` | **File** — one `*.transitrix.yaml` at a time | Schema, required fields, notation-specific rules for all 15 canonical extensions (no `--ext` flag needed for canonical notations) | On every save; pre-commit hook; CI per-file job | **Active — canonical** |
| `python3 .validators/lint.py` | **Repo** — whole `canon/` tree at once | Atomicity (no relations inside element files), referential integrity, ArchiMate semantics, policy (Active status requires an owner) | Before every PR; CI repo-integrity job | **Active — canonical.** Fetched from `tools/lint.py` at scaffold time; lives at `.validators/lint.py` in the adopter repo |
| `python3 tools/knowledge_store_lint.py <root>` | **Knowledge store** — `_intake/processed/` + `_intake/drafts/` + `knowledge/` | Quality Gates 1–6: quarantine, canonical-definition, blast-radius tiering, provenance/confidence, scoped consistency, assisted ingest — per `patterns/knowledge-store.md` | When proposing or promoting knowledge objects; when reviewing the store for drift | **Methodology-internal reference implementation.** Adopters may copy and adapt it; the methodology owns the rules, not this script. Documented in [`patterns/knowledge-store.md`](../patterns/knowledge-store.md) |
| `python3 tools/knowledge_store_lint.py <root>` | **Knowledge store** — `sources/` (and legacy `_intake/processed/`) + `_intake/drafts/` + `knowledge/` | Quality Gates 1–6: quarantine, canonical-definition, blast-radius tiering, provenance/confidence, scoped consistency, assisted ingest — per `patterns/knowledge-store.md` | When proposing or promoting knowledge objects; when reviewing the store for drift | **Methodology-internal reference implementation.** Adopters may copy and adapt it; the methodology owns the rules, not this script. Documented in [`patterns/knowledge-store.md`](../patterns/knowledge-store.md) |
| `python3 tools/check_views_compliance.py` | Methodology-repo only | Notation-specific rules on the methodology's own example files | Not for adopters | **Deprecated** — superseded by `@transitrix/cli`. Kept for migration reference only |

### Requirement-chain consumer support
Expand Down
32 changes: 25 additions & 7 deletions packages/ingest-cli/src/intake-profile.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Skill-local deployment compatibility. Read-only checks precede every setup write.
import { lstat, readdir, readFile, mkdir, writeFile } from 'node:fs/promises';
import { join, resolve, relative } from 'node:path';
import { basename, join, resolve, relative } from 'node:path';
import { spawnSync } from 'node:child_process';

const PROFILES = ['ingest', 'knowledge-store'];
Expand Down Expand Up @@ -33,6 +33,16 @@ async function files(path) {
return result;
}

async function sourceRecordType(path, legacy = false) {
const text = path.endsWith('.md') ? await readFile(path, 'utf8') : '';
const frontmatter = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/.exec(text)?.[1] || '';
const match = /^(?:type|["']type["']):[ \t]*(["']?)(source-document|source-redirect)\1[ \t]*(?:#.*)?$/m.exec(frontmatter);
if (legacy && !match && /source-(?:document|redirect)/.test(frontmatter)) {
fail(`ambiguous source record at ${path}`);
}
return match?.[2] ?? null;
}

export async function checkIntakeProfile(orgRoot, profile) {
if (!PROFILES.includes(profile)) throw new Error('Choose --profile ingest or --profile knowledge-store');
const root = resolve(orgRoot);
Expand All @@ -58,12 +68,20 @@ export async function checkIntakeProfile(orgRoot, profile) {
if (stat && !stat.isDirectory()) fail(`_intake/${name} is not a directory`);
}
const processed = await files(join(root, '_intake/processed'));
const sourceDir = join(root, 'sources');
const sourceDirStat = await inspect(sourceDir);
if (profile === 'knowledge-store' && sourceDirStat && !sourceDirStat.isDirectory()) fail('sources is not a directory');
const sourceFiles = sourceDirStat?.isDirectory()
? (await files(sourceDir)).filter(path => path.endsWith('.md') && !['index.md', 'log.md'].includes(basename(path)))
: [];
for (const path of sourceFiles) {
if (await sourceRecordType(path) === 'source-document') {
evidence['knowledge-store'].push(relative(root, path));
}
}
for (const path of processed) {
const text = path.endsWith('.md') ? await readFile(path, 'utf8') : '';
const frontmatter = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/.exec(text)?.[1] || '';
const okf = /^(?:type|["']type["']):[ \t]*(?:"source-document"|'source-document'|source-document)[ \t]*(?:#.*)?$/m.test(frontmatter);
if (!okf && frontmatter.includes('source-document')) fail(`ambiguous source record at ${relative(root, path)}`);
evidence[okf ? 'knowledge-store' : 'ingest'].push(relative(root, path));
const type = await sourceRecordType(path, true);
evidence[type ? 'knowledge-store' : 'ingest'].push(relative(root, path));
}
// Unknown workspace entries must be classified by the adopter, never guessed.
if (intake) {
Expand All @@ -85,7 +103,7 @@ export async function checkIntakeProfile(orgRoot, profile) {

if (profile === 'knowledge-store') {
// Git evaluates nested/global ignores and negations, including tracked files.
const paths = ['_intake/processed/profile-check.md', ...processed.map(p => relative(root, p))];
const paths = ['sources/profile-check.md', ...[...sourceFiles, ...processed].map(p => relative(root, p))];
const result = spawnSync('git', ['-C', root, 'check-ignore', '--no-index', '--stdin'], {
input: paths.join('\n') + '\n', encoding: 'utf8',
});
Expand Down
23 changes: 23 additions & 0 deletions packages/ingest-cli/src/intake-profile.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ test('OKF selection refuses ingest setup and preserves source and original bytes

for (const [path, content, requested] of [
['_intake/processed/source.md', okf, 'ingest'],
['sources/source.md', okf, 'ingest'],
['knowledge/index.md', '# Knowledge', 'ingest'],
['_intake/originals/source.txt', 'original', 'ingest'],
['_intake/drafts/draft.md', 'draft', 'ingest'],
Expand Down Expand Up @@ -105,12 +106,34 @@ for (const [path, ignore] of [
['.git/info/exclude', '_intake/processed/**\n'],
]) test(`OKF refuses effective ignore rules in ${path}`, async t => {
const root = await workspace(t);
await put(root, '_intake/processed/source.md', okf);
await put(root, path, ignore);
const before = await snapshot(root);
await assert.rejects(selectIntakeProfile(root, 'knowledge-store'), /profile conflict/);
assert.deepEqual(await snapshot(root), before);
});

test('new layout checks source cards without reserving an unused legacy directory', async t => {
const root = await workspace(t);
await put(root, '.gitignore', '_intake/processed/\n');
await checkIntakeProfile(root, 'knowledge-store');
await put(root, 'sources/team/source.md', okf);
await checkIntakeProfile(root, 'knowledge-store');
await put(root, 'sources/team/.gitignore', '*.md\n');
await assert.rejects(checkIntakeProfile(root, 'knowledge-store'), /would be ignored/);
});

test('legacy redirects count as Knowledge Store evidence and remain visible', async t => {
const root = await workspace(t);
await put(root, 'sources/source.md', okf);
await put(root, '_intake/processed/source.md',
'---\ntype: source-redirect\nredirect_to: /sources/source.md\n---\n');
await checkIntakeProfile(root, 'knowledge-store');
await assert.rejects(checkIntakeProfile(root, 'ingest'), /knowledge-store evidence/);
await put(root, '.gitignore', '_intake/processed/\n');
await assert.rejects(checkIntakeProfile(root, 'knowledge-store'), /would be ignored/);
});

test('Git negation permits OKF; ignored tracked records are still refused', async t => {
const root = await workspace(t);
await put(root, '.gitignore', '_intake/processed/*\n!_intake/processed/*.md\n');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ type: insight | finding | concept | question
title: ""
description: ""
resource: ""
source: /_intake/processed/source-document-name.md
source: /sources/source-document-name.md
created_at: YYYY-MM-DD
confidence: observed | inferred | assumed
mapping: confirms | extends | proposes | conflicts # optional — Gate 2 classification
Expand All @@ -22,7 +22,7 @@ _Full description of the knowledge object._

# Citations

- [Source document title](/_intake/processed/source-document-name.md)
- [Source document title](/sources/source-document-name.md)

# Examples

Expand Down
4 changes: 2 additions & 2 deletions patterns/knowledge-store-templates/okf-knowledge-object.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ type: insight | finding | concept | question
title: ""
description: ""
resource: ""
source: /intake/processed/source-document-name.md
source: /sources/source-document-name.md
created_at: YYYY-MM-DD
confidence: observed | inferred | assumed
mapping: confirms | extends | proposes | conflicts # optional — Gate 2 classification when admitting
Expand All @@ -21,7 +21,7 @@ _Full description of the knowledge object._

# Citations

- [Source document title](/intake/processed/source-document-name.md)
- [Source document title](/sources/source-document-name.md)

# Examples

Expand Down
Loading
Loading