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
24 changes: 21 additions & 3 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:

strategy:
matrix:
node-version: [18.x, 20.x, 22.x]
node-version: [22.x, 24.x]

steps:
- name: Checkout repository
Expand Down Expand Up @@ -56,7 +56,7 @@ jobs:
echo "RECON self-documentation completed successfully"

portability:
name: OS portability / ${{ matrix.os }} / Node 20
name: OS portability / ${{ matrix.os }} / Node 24
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
Expand All @@ -72,7 +72,7 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20.x
node-version: 24.x
cache: npm
cache-dependency-path: package-lock.json

Expand All @@ -99,3 +99,21 @@ jobs:
- name: Verify public-source release policy
run: npm run release:check

required:
name: required
if: ${{ always() }}
needs:
- test
- portability
runs-on: ubuntu-latest
steps:
- name: Verify mandatory test jobs succeeded
env:
TEST_RESULT: ${{ needs.test.result }}
PORTABILITY_RESULT: ${{ needs.portability.result }}
run: |
if [ "$TEST_RESULT" != "success" ] || [ "$PORTABILITY_RESULT" != "success" ]; then
echo "Required test gate failed: test=$TEST_RESULT portability=$PORTABILITY_RESULT"
exit 1
fi

2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ linking are the supported ways to experiment with it today.

Prerequisites:

- Node.js 18 or later;
- Node.js 22 or later;
- npm;
- Python 3 for Python extractor development.

Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ repository exploration, task-scoped context, workspace queries, and evidence
production without replacing canonical architecture or governance authority.

[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](LICENSE)
[![Node](https://img.shields.io/badge/node-%3E%3D18.0.0-brightgreen.svg)](https://nodejs.org/)
[![Node](https://img.shields.io/badge/node-%3E%3D22.0.0-brightgreen.svg)](https://nodejs.org/)

## Runtime boundary and AI orientation

Expand All @@ -26,7 +26,7 @@ human-in-the-loop; the runtime is not an autonomous engineering agent.

## Quick Start

The supported path is a source checkout. Requires Node.js 18 or later and npm.
The supported path is a source checkout. Requires Node.js 22 or later and npm.
Python 3 is also required by some extractors.

```bash
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,16 @@ interaction_contracts: []
constraints: []
invariants:
- id: 019ff84e-4ece-7387-b33f-3d203e3c968c
statement: 'Single repository only: RECON discovers files within the current repository.
Cross-repository reconciliation is out of scope.'
statement: 'RECON extraction is repository-local: each repository observation discovers
files within its registered repository source. Workspace orchestration may compose
multiple repository observations into one derived workspace projection. Cross-workspace
reconciliation remains out of scope unless explicitly federated.'
scope: global
enforcement_level: must
enforcement_mechanism: design
verification_method: manual
rationale: Extracted from ADR-L-0001 specification
rationale: Repository source remains the provenance boundary while workspace orchestration
provides the bounded multi-repository graph shell.
compliance_frameworks: []
exceptions: []
alias_id: INV-0001
Expand Down
13 changes: 11 additions & 2 deletions adrs/logical/ADR-L-0009-unified-workspace-scope-model.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@ context: 'Reconnaissance tools traditionally assume a single repository as the u

level so that cross-repo relationships, shared configuration, and

aggregate evidence can be captured correctly.
aggregate evidence can be captured correctly. A workspace is a durably identified
engineering scope composed of one or more repositories. Repositories are the only
workspace membership units; graph entities remain derived projections with repository
provenance.

'
capabilities:
Expand Down Expand Up @@ -68,7 +71,13 @@ decision: 'Scope is a workspace. A workspace contains one or more repositories.

is --workspace <dir> where the workspace contains one repo. No

special-case code exists for single-repo mode.
special-case code exists for single-repo mode. Workspace identity is an immutable
UUIDv7 minted by explicit registration creation. Registration-scoped repository
UUIDv7 identities preserve provenance across local materialization path changes.
Definition revisions are runtime-generated canonical digests and do not change
workspace identity. Repository boundaries preserve source provenance; the workspace
boundary governs graph traversal. Cross-workspace traversal is fail-closed unless
future federation is explicitly declared.

'
consequences:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,10 @@ context: 'ADR-L-0009 fixes workspace as the universal scope unit. This ADR recor

phase-level extraction behavior; workspace orchestration layered here only

coordinates repos and aggregates outcomes.
coordinates repos and aggregates outcomes. The public runtime adapter treats each
refresh as an immutable observation event: observed repositories alone contribute
current graph content, partial observations contain no stale failed-repository
projection, and zero observed repositories produce a typed refresh failure.

'
implements_physical_system_ref:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,12 @@ context: 'The workspace semantic graph (verb-typed edges in slices/*.yaml, produ

API. loadAidocGraph() only consumes per-repo RECON YAML with _slice blocks. A

new loader and query layer was required.
new loader and query layer was required. Public graph references are scoped by
workspace and immutable snapshot identity; legacy node IDs are opaque projection
keys rather than durable source/entity identity. Repository identity is carried as
separate node and relationship provenance. Traversal is bounded by one workspace
and one snapshot projection and rejects foreign workspace or snapshot endpoints.
The runtime performs no semantic or probabilistic reasoning.

'
capabilities:
Expand Down
2 changes: 1 addition & 1 deletion documentation/guides/workspace-initialization.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ For single-repo usage, see [RECON-README.md](../../instructions/RECON-README.md)

## Prerequisites

- Node.js 18+
- Node.js 22+
- npm
- ste-runtime cloned and built:

Expand Down
10 changes: 5 additions & 5 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 10 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@
"type": "module",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js",
"default": "./dist/index.js"
}
},
"files": [
"dist",
"README.md",
Expand All @@ -27,6 +34,7 @@
"test:watch": "vitest",
"test:unit": "vitest run",
"test:integration": "node scripts/prove-architecture-compile.mjs",
"test:public-tarball": "node scripts/prove-public-tarball.mjs",
"release:check": "node scripts/check-release-policy.mjs",
"test:coverage": "vitest run --coverage",
"lint": "eslint src vitest.config.ts",
Expand Down Expand Up @@ -61,7 +69,7 @@
"@eslint/js": "^9.22.0",
"@mermaid-js/mermaid-cli": "^11.4.0",
"@types/js-yaml": "^4.0.9",
"@types/node": "^20.0.0",
"@types/node": "^22.0.0",
"@vitest/coverage-v8": "^3.2.0",
"ajv": "^8.18.0",
"eslint": "^9.22.0",
Expand All @@ -70,7 +78,7 @@
"vitest": "^3.2.0"
},
"engines": {
"node": ">=18.0.0"
"node": ">=22.0.0"
},
"packageManager": "npm@10.9.4",
"keywords": [
Expand Down
4 changes: 2 additions & 2 deletions scripts/init.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ ${c.cyan}EXAMPLES:${c.reset}
node scripts/init.cjs --mcp

${c.cyan}WHAT THIS DOES:${c.reset}
1. Validates prerequisites (Node.js 18+, npm)
1. Validates prerequisites (Node.js 22+, npm)
2. Installs dependencies (npm install)
3. Builds the project (npm run build)
4. Runs initial RECON to create semantic graph
Expand Down Expand Up @@ -405,7 +405,7 @@ function printSummary(success, options) {
log(`${c.red}${c.bold}Bootstrap failed. See errors above.${c.reset}`);
log('');
log('Common fixes:');
log(' 1. Ensure Node.js 18+ is installed');
log(' 1. Ensure Node.js 22+ is installed');
log(' 2. Run from the ste-runtime-private directory');
log(' 3. Check network connectivity for npm install');
log(' 4. Try: rm -rf node_modules && npm install');
Expand Down
94 changes: 94 additions & 0 deletions scripts/prove-public-tarball.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import fs from 'node:fs/promises';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { execFile } from 'node:child_process';
import { promisify } from 'node:util';

const execFileAsync = promisify(execFile);
const repositoryRoot = path.resolve(fileURLToPath(new URL('../', import.meta.url)));
const fixtureRoot = path.join(repositoryRoot, 'test', 'fixtures', 'public-consumer');
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'ste-runtime-public-tarball-'));
const consumerRoot = path.join(tempRoot, 'consumer');
const packRoot = path.join(tempRoot, 'pack');
const sourceFixtureRoot = path.join(consumerRoot, 'source-fixture');
const packNpmCache = path.join(tempRoot, 'npm-pack-cache');
const installNpmCache = process.env.npm_config_cache
?? (process.env.LOCALAPPDATA ? path.join(process.env.LOCALAPPDATA, 'npm-cache') : undefined);
const npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm';
const tscCommand = process.platform === 'win32'
? path.join(consumerRoot, 'node_modules', '.bin', 'tsc.cmd')
: path.join(consumerRoot, 'node_modules', '.bin', 'tsc');

try {
await fs.mkdir(consumerRoot, { recursive: true });
await fs.mkdir(packRoot, { recursive: true });
await fs.copyFile(path.join(fixtureRoot, 'package.json'), path.join(consumerRoot, 'package.json'));
await fs.copyFile(path.join(fixtureRoot, 'consumer.ts'), path.join(consumerRoot, 'consumer.ts'));
await fs.copyFile(path.join(fixtureRoot, 'bootstrap.mjs'), path.join(consumerRoot, 'bootstrap.mjs'));
await fs.copyFile(path.join(fixtureRoot, 'tsconfig.json'), path.join(consumerRoot, 'tsconfig.json'));
await fs.mkdir(path.join(sourceFixtureRoot, 'src'), { recursive: true });
await fs.writeFile(path.join(sourceFixtureRoot, 'package.json'), '{"name":"public-consumer-source-fixture","version":"1.0.0"}\n');
await fs.writeFile(path.join(sourceFixtureRoot, 'src', 'index.ts'), 'export const fixture = 1;\n');

await execFileAsync(npmCommand, ['run', 'build'], {
cwd: repositoryRoot,
maxBuffer: 4 * 1024 * 1024,
shell: true,
env: { ...process.env, npm_config_cache: packNpmCache },
});

const { stdout } = await execFileAsync(npmCommand, ['pack', '--ignore-scripts', '--json', '--pack-destination', packRoot], {
cwd: repositoryRoot,
maxBuffer: 1024 * 1024,
shell: true,
env: {
...process.env,
npm_config_cache: packNpmCache,
},
});
const packed = JSON.parse(stdout.slice(stdout.indexOf('[')));
const tarball = path.resolve(packRoot, packed[0].filename);

await execFileAsync(npmCommand, [
'install', tarball, '--ignore-scripts', '--no-audit', '--no-fund', '--no-package-lock',
], {
cwd: consumerRoot,
maxBuffer: 4 * 1024 * 1024,
shell: true,
env: {
...process.env,
...(installNpmCache ? { npm_config_cache: installNpmCache } : {}),
npm_config_fetch_retries: '0',
npm_config_fetch_timeout: '10000',
},
});

await execFileAsync(tscCommand, ['--project', 'tsconfig.json'], {
cwd: consumerRoot,
maxBuffer: 4 * 1024 * 1024,
shell: true,
});

const result = await execFileAsync(process.execPath, ['bootstrap.mjs', sourceFixtureRoot], {
cwd: consumerRoot,
maxBuffer: 1024 * 1024,
});
for (const forbidden of ['.ste', '.ste-self', '.workspace-graph']) {
const candidate = path.join(sourceFixtureRoot, forbidden);
await expectMissing(candidate);
}
process.stdout.write(result.stdout);
} finally {
await fs.rm(tempRoot, { recursive: true, force: true });
}

async function expectMissing(candidate) {
try {
await fs.access(candidate);
throw new Error(`Packed consumer created forbidden state path: ${candidate}`);
} catch (error) {
if (error && typeof error === 'object' && 'code' in error && error.code === 'ENOENT') return;
throw error;
}
}
Loading
Loading