Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -676,7 +676,7 @@ module(`server-endpoints/${basename(__filename)}`, function (hooks) {
context.testRealmServer as unknown as { realms: { url: string }[] }
).realms;
let publishedRealmIndex = mountedRealms.findIndex(
(realm) => realm.url === publishedRealmURL,
(realm: { url: string }) => realm.url === publishedRealmURL,
);
assert.notStrictEqual(
publishedRealmIndex,
Expand Down
68 changes: 67 additions & 1 deletion packages/software-factory/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ startup do not require a separate external realm server on `http://localhost:420
- `pnpm smoke:realm`
- Boots the isolated realm server, fetches `project-demo` as card JSON, and exits
- `pnpm factory:go -- --brief-url <url> --target-realm-path <path>`
- Validates one-shot factory inputs and prints a machine-readable run summary
- Fetches and normalizes a brief, validates one-shot inputs, and prints a machine-readable run summary
- `pnpm test`
- Runs package tests from `tests/*.test.ts` and `tests/*.spec.ts`
- `pnpm test:node`
Expand Down Expand Up @@ -71,6 +71,7 @@ Usage:
pnpm factory:go -- \
--brief-url http://localhost:4201/software-factory/Wiki/sticky-note \
--target-realm-path /path/to/target-realm \
[--auth-token "<authorization-header-value>"] \
[--target-realm-url http://localhost:4201/hassan/personal/] \
[--mode implement]
```
Expand All @@ -79,6 +80,10 @@ Parameters:

- `--brief-url`
- Required. Absolute URL for the source brief card the factory should use as input.
- The command fetches card source JSON from this URL and includes normalized brief metadata in the summary.
- `--auth-token`
- Optional. Explicit `Authorization` header value to use when fetching the brief.
- This is an override. You usually do not need it unless you already have a token and want to force that exact header value.
- `--target-realm-path`
- Required. Local filesystem path to the Boxel realm where the factory should write output.
- `--target-realm-url`
Expand All @@ -88,6 +93,67 @@ Parameters:
- `--help`
- Optional. Prints the command usage and exits.

Auth for fetching private briefs:

- If the brief is in a public realm, you do not need any auth setup.
- If the brief is in a private realm, `factory:go` can usually authenticate without `--auth-token`. It will try these sources in order:
- the active Boxel profile in `~/.boxel-cli/profiles.json`
- `MATRIX_URL`, `MATRIX_USERNAME`, `MATRIX_PASSWORD`, and `REALM_SERVER_URL`
- `MATRIX_URL`, `REALM_SERVER_URL`, and `REALM_SECRET_SEED`
- When using `REALM_SECRET_SEED`, `factory:go` can derive the realm username from the brief URL when `MATRIX_USERNAME` is not set.

Private brief with explicit Matrix username/password env:

```bash
export MATRIX_URL=http://localhost:8008/
export MATRIX_USERNAME=factory
read -s MATRIX_PASSWORD'?Matrix password: '
export MATRIX_PASSWORD
export REALM_SERVER_URL=http://localhost:4201/

pnpm factory:go -- \
--brief-url http://localhost:4201/software-factory/Wiki/sticky-note \
--target-realm-path /path/to/target-realm
```

Private brief with `REALM_SECRET_SEED` instead of username/password:

```bash
export MATRIX_URL=http://localhost:8008/
export REALM_SERVER_URL=http://localhost:4201/
read -s REALM_SECRET_SEED'?Realm secret seed: '
export REALM_SECRET_SEED

pnpm factory:go -- \
--brief-url http://localhost:4201/software-factory/Wiki/sticky-note \
--target-realm-path /path/to/target-realm
```

Getting an auth token explicitly:

- Only use this when you specifically want to pass `--auth-token`. `factory:go` itself does not require this if one of the auth sources above is already configured.
- `pnpm boxel:session` can authenticate from the active Boxel profile or from `MATRIX_URL`, `MATRIX_USERNAME`, `MATRIX_PASSWORD`, and `REALM_SERVER_URL`.
- `pnpm boxel:session` does not require `--realm`. Passing `--realm` only narrows the returned `boxelSession` object to specific realms.

Example:

```bash
export MATRIX_URL=http://localhost:8008/
export MATRIX_USERNAME=factory
read -s MATRIX_PASSWORD'?Matrix password: '
export MATRIX_PASSWORD
export REALM_SERVER_URL=http://localhost:4201/

AUTH_TOKEN="$(pnpm --silent boxel:session | jq -r '.boxelSession[\"http://localhost:4201/software-factory/\"]')"

pnpm factory:go -- \
--brief-url http://localhost:4201/software-factory/Wiki/sticky-note \
--auth-token "$AUTH_TOKEN" \
--target-realm-path /path/to/target-realm
```

- When you do use `--auth-token`, pass the exact `Authorization` header value returned by `pnpm boxel:session`.

## Layout

- `test-fixtures/darkfactory-adopter/`
Expand Down
30 changes: 22 additions & 8 deletions packages/software-factory/docs/one-shot-factory-go-plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,8 @@ Required behavior:

- fetch the brief card JSON
- normalize the brief into a concise internal representation
- detect whether the brief is vague
- if vague, automatically bias toward a thin MVP
- prepare a prompt for the AI to decide whether to default to a thin MVP
- prepare a prompt for the AI to create clarification or review tickets when the brief needs more guidance

### Phase 2: Target Realm Preparation

Expand Down Expand Up @@ -243,9 +243,15 @@ The first version should support:
Add a script:

```json
"factory:go": "ts-node --esm --transpileOnly scripts/factory-go.ts"
"factory:go": "ts-node --transpileOnly src/cli/factory-entrypoint.ts"
```

For software-factory CLI entrypoints, favor `ts-node --transpileOnly` over `tsx`.

- it matches the execution model already used by `realm-server`
- it avoids the decorator/runtime incompatibilities we hit when `tsx` imports `runtime-common` auth code
- it keeps package CLI entrypoints aligned with the `runtime-common` auth infrastructure instead of forcing parallel implementations

Expected usage:

```bash
Expand Down Expand Up @@ -333,15 +339,16 @@ Responsibilities:

- fetch a brief card by URL
- extract useful fields from card JSON
- normalize vague briefs into a simple planning shape
- normalize the brief into a concise planning input
- emit metadata like:
- title
- summary
- content
- source URL
- ambiguity score or `isVague` flag
- structured fields that a later AI stage can use for thin-MVP vs broader-first-pass planning
- enough context for later clarification and review follow-up ticket decisions

For version one, the `isVague` check can be heuristic and simple.
For version one, this helper can stay deterministic and data-oriented. Later AI stages should combine the structured brief fields with a stable prompt template rather than embedding a fully rendered prompt into `factory:go` output.

### E. `scripts/lib/factory-loop.ts`

Expand Down Expand Up @@ -446,7 +453,8 @@ Optional later additions:
"brief": {
"url": "http://localhost:4201/software-factory/Wiki/sticky-note",
"title": "Sticky Note",
"isVague": true
"contentSummary": "Colorful, short-form note designed for spatial arrangement on boards and artboards.",
"tags": ["documents-content", "sticky", "note"]
},
"targetRealm": {
"path": "/.../personal",
Expand All @@ -472,14 +480,20 @@ Optional later additions:

This keeps the process inspectable and resumable.

Brief intake should not assume public realm access.

- `factory:go` should fetch the brief with `Accept: application/vnd.card+source`
- when the brief URL is on the active Boxel realm-server origin, the CLI should try to resolve a realm JWT from the active Boxel profile before fetching
- the CLI should also allow an explicit brief auth override for cases where the caller already has an `Authorization` header value to use

## Acceptance Criteria For The First `factory:go`

- a user can point to a brief URL and a target realm path
- the target realm ends up with a coherent project bootstrap
- exactly one ticket becomes active
- rerunning does not create duplicate starter artifacts
- the flow can proceed directly into implementation work
- the system prefers a thin MVP when the brief is vague
- the brief normalization output gives the AI enough context to choose thin-MVP vs broader-first-pass planning and request clarification or review tickets when needed

## Recommended Delivery Order

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,8 +140,11 @@ These should be covered with unit tests and focused integration tests.
Hermetic requirement for this layer:

- deterministic `factory:go` tests must not depend on an ambient realm server on `http://localhost:4201/`
- deterministic `factory:go` tests must not hard-code `localhost:4201` or port `4201` just to get an absolute URL shape
- when a test only needs an absolute URL shape, use a synthetic URL such as `https://briefs.example.test/...`
- prefer reserved synthetic hosts such as `*.example.test` or dynamically assigned local test-server ports over canonical dev ports
- when a test needs a live realm, use the isolated software-factory harness rather than external local infrastructure
- the only acceptable exceptions are harness-level redirect tests that intentionally intercept a canonical realm URL without depending on a server actually listening on that port

Debugging note:

Expand Down
11 changes: 5 additions & 6 deletions packages/software-factory/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,18 @@
"boxel:pick-ticket": "NODE_NO_WARNINGS=1 ts-node --transpileOnly scripts/pick-ticket.ts",
"boxel:search": "NODE_NO_WARNINGS=1 ts-node --transpileOnly scripts/boxel-search.ts",
"boxel:session": "NODE_NO_WARNINGS=1 ts-node --transpileOnly scripts/boxel-session.ts",
"cache:prepare": "tsx src/cli/cache-realm.ts",
"factory:go": "tsx src/cli/factory-entrypoint.ts",
"cache:prepare": "NODE_NO_WARNINGS=1 ts-node --transpileOnly src/cli/cache-realm.ts",
"factory:go": "NODE_NO_WARNINGS=1 ts-node --transpileOnly src/cli/factory-entrypoint.ts",
"lint": "concurrently \"pnpm:lint:*(!fix)\" --names \"lint:\"",
"lint:fix": "concurrently \"pnpm:lint:*:fix\" --names \"fix:\"",
"lint:js": "eslint . --report-unused-disable-directives --cache",
"lint:js:fix": "eslint . --report-unused-disable-directives --fix",
"lint:format": "prettier --check .",
"lint:format:fix": "prettier --write .",
"lint:glint": "glint",
"serve:realm": "tsx src/cli/serve-realm.ts",
"serve:support": "tsx src/cli/serve-support.ts",
"smoke:realm": "tsx src/cli/smoke-realm.ts",
"serve:realm": "NODE_NO_WARNINGS=1 ts-node --transpileOnly src/cli/serve-realm.ts",
"serve:support": "NODE_NO_WARNINGS=1 ts-node --transpileOnly src/cli/serve-support.ts",
"smoke:realm": "NODE_NO_WARNINGS=1 ts-node --transpileOnly src/cli/smoke-realm.ts",
"test": "NODE_NO_WARNINGS=1 ts-node --transpileOnly scripts/test.ts",
"test:all": "NODE_NO_WARNINGS=1 ts-node --transpileOnly scripts/test.ts",
"test:node": "NODE_NO_WARNINGS=1 ts-node --transpileOnly scripts/test.ts --node-only",
Expand Down Expand Up @@ -55,7 +55,6 @@
"qunit": "catalog:",
"tmp": "catalog:",
"ts-node": "^10.9.1",
"tsx": "^4.21.0",
"typescript": "catalog:"
},
"volta": {
Expand Down
42 changes: 25 additions & 17 deletions packages/software-factory/src/cli/factory-entrypoint.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,35 @@
import {
FactoryEntrypointUsageError,
buildFactoryEntrypointSummary,
getFactoryEntrypointUsage,
parseFactoryEntrypointArgs,
runFactoryEntrypoint,
wantsFactoryEntrypointHelp,
} from '../factory-entrypoint';
import { FactoryBriefError } from '../factory-brief';

try {
if (wantsFactoryEntrypointHelp(process.argv.slice(2))) {
console.log(getFactoryEntrypointUsage());
process.exit(0);
}
async function main(): Promise<void> {
try {
if (wantsFactoryEntrypointHelp(process.argv.slice(2))) {
console.log(getFactoryEntrypointUsage());
return;
}

let options = parseFactoryEntrypointArgs(process.argv.slice(2));
let summary = await runFactoryEntrypoint(options);
console.log(JSON.stringify(summary, null, 2));
} catch (error) {
if (error instanceof FactoryEntrypointUsageError) {
console.error(error.message);
console.error('');
console.error(getFactoryEntrypointUsage());
} else if (error instanceof FactoryBriefError) {
console.error(error.message);
} else {
console.error(error);
}

let options = parseFactoryEntrypointArgs(process.argv.slice(2));
let summary = buildFactoryEntrypointSummary(options);
console.log(JSON.stringify(summary, null, 2));
} catch (error) {
if (error instanceof FactoryEntrypointUsageError) {
console.error(error.message);
console.error('');
console.error(getFactoryEntrypointUsage());
} else {
console.error(error);
process.exitCode = 1;
}
process.exit(1);
}

void main();
Loading
Loading