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
12 changes: 12 additions & 0 deletions .changeset/open-loop-software-development-preset.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
'@gemstack/ai-autopilot': minor
---

Ship the "Software Development" domain preset (#243).

The first built-in Open Loop preset, authored as a directory of `.md` files:
two loops (major-change -> code-review + test-coverage + security-review; bug-fix
-> root-cause + regression-test), five stack-agnostic prompt bodies, and one skill
pointer. Non-web and user-picked (no dependency detection). Load it with
`softwareDevelopmentPreset()`; `builtinPresetsDir()` points at the shipped
`presets/` directory. Proves the bundle unit end to end.
3 changes: 2 additions & 1 deletion packages/ai-autopilot/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@
},
"files": [
"dist",
"prompts"
"prompts",
"presets"
],
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
name: bug-fix-loop
description: What fires after a bug fix.
metadata:
on: bug-fix
run: [root-cause, regression-test]
---

When the agent fixes a bug, confirm the root cause is understood and addressed
(not just the symptom), then lock it in with a regression test.
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
name: major-change-loop
description: What fires after a substantial code change.
metadata:
on: major-change
run: [code-review, test-coverage, security-review]
---

When the agent lands a substantial change, review it, check it is covered by
tests, then look for security regressions — in that order.
10 changes: 10 additions & 0 deletions packages/ai-autopilot/presets/software-development/preset.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
name: software-development
description: A stack-agnostic engineering loop — review, tests, and security on every substantial change, root-cause and regression coverage on every fix.
metadata:
title: Software Development
---

The general software engineering domain. Not tied to a web framework: it runs the
same review / test / security discipline over any codebase. Pick it when you want
solid engineering hygiene without a stack-specific preset.
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
---
name: code-review
description: Review a change for correctness, clarity, and design.
appliesTo: ["**/*"]
metadata:
title: Code review
loopId: code-review
passes: 1
event: major-change
---

You are reviewing a substantial change. Read the diff and the code around it, then
report the issues that actually matter — correctness first, then clarity and design.

Focus on:
- **Correctness** — logic errors, unhandled cases, off-by-one, race conditions, wrong assumptions.
- **Clarity** — names, control flow, and comments a maintainer will thank or curse you for.
- **Design** — does the change fit the surrounding code, or does it fork a second way to do one thing?

For each finding give one line on what is wrong and one line on the fix. Skip nits
the linter already catches. If the change is sound, say so plainly and stop.
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
---
name: regression-test
description: Ensure a fix is locked in by a test that fails without it.
appliesTo: ["**/*"]
metadata:
title: Regression test
loopId: regression-test
passes: 1
event: bug-fix
---

You are making sure a bug fix is locked in by a regression test — one that fails on
the old code and passes on the new.

Check:
- Is there a test that reproduces the original bug and would fail without this fix?
- Does it assert the corrected behavior, not just the absence of the crash?
- Is it placed and named so a future reader knows which bug it guards?

If the regression test is missing or weak, describe the exact case it should cover.
If it is already present and meaningful, confirm it and stop.
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
name: root-cause
description: Confirm a fix addresses the root cause, not the symptom.
appliesTo: ["**/*"]
metadata:
title: Root cause
loopId: root-cause
passes: 1
event: bug-fix
---

You are checking that a bug fix addresses the **root cause**, not just the symptom
that was reported.

Work backwards from the fix:
- What was the actual defect, and why did it produce the observed symptom?
- Does the fix remove the defect, or does it mask it (a guard, a retry, a catch that swallows)?
- Could the same root cause surface elsewhere in the codebase through a different path?

State the root cause in one sentence. If the fix only treats the symptom, say what
the real fix is. If it correctly removes the cause, confirm it and note any sibling
sites worth the same treatment.
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
name: security-review
description: Look for security regressions introduced by the change.
appliesTo: ["**/*"]
metadata:
title: Security review
loopId: security-review
passes: 1
event: major-change
---

You are checking a change for security regressions. Scope the review to what the
change touches; do not audit the whole codebase.

Look for:
- Untrusted input reaching a sink (injection, path traversal, deserialization).
- Authn/authz gaps — a route, action, or resource that skips a check the neighbors make.
- Secrets, tokens, or PII in code, logs, or error messages.
- Unsafe defaults and dependencies added with known advisories.

Report each concrete risk with the file, why it is exploitable, and the fix. If the
change introduces no new exposure, say so and stop.
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
---
name: test-coverage
description: Check the change is covered by meaningful tests.
appliesTo: ["**/*"]
metadata:
title: Test coverage
loopId: test-coverage
passes: 1
event: major-change
---

You are checking that a substantial change is covered by tests that would actually
catch a regression — not coverage for its own sake.

Ask:
- Does every new branch and edge case have a test that fails if the behavior breaks?
- Are the tests asserting behavior, or just that the code ran without throwing?
- Is anything important only exercised by a happy-path test?

Name the specific untested paths and, for each, the one test worth adding. If the
change is a pure refactor with existing coverage, say so and stop.
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
name: eng-practices
description: Google's engineering practices — code review and change-authoring guidelines.
metadata:
title: Engineering Practices
url: https://google.github.io/eng-practices/
---

A stack-agnostic reference for what a good change and a good review look like:
the reviewer standard, what to look for, and how to write a change that is easy to
review. Consult it when framing review and authoring work.
3 changes: 3 additions & 0 deletions packages/ai-autopilot/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@
* - {@link defineDomainPreset} / {@link loadDomainPreset} — author, or load from a directory
* - {@link composeDomainPresets} — merge presets into one (later wins on prompt/skill id)
* - {@link selectPreset} — pick the user's chosen domain by name
* - {@link softwareDevelopmentPreset} — the shipped, stack-agnostic built-in
*/
export { Supervisor } from './supervisor.js'
export { agentPlanner, type AgentPlannerOptions } from './planner.js'
Expand Down Expand Up @@ -336,6 +337,8 @@ export {
loadDomainPreset,
loadLoopsFrom,
loadSkillsFrom,
builtinPresetsDir,
softwareDevelopmentPreset,
type DomainPreset,
type DomainPresetSpec,
type DomainPresetMeta,
Expand Down
8 changes: 7 additions & 1 deletion packages/ai-autopilot/src/preset/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,11 @@
*/
export { defineDomainPreset, DomainPresetError } from './define.js'
export { composeDomainPresets, selectPreset } from './compose.js'
export { loadDomainPreset, loadLoopsFrom, loadSkillsFrom } from './load.js'
export {
loadDomainPreset,
loadLoopsFrom,
loadSkillsFrom,
builtinPresetsDir,
softwareDevelopmentPreset,
} from './load.js'
export type { DomainPreset, DomainPresetSpec, DomainPresetMeta } from './types.js'
12 changes: 12 additions & 0 deletions packages/ai-autopilot/src/preset/load.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { readFile, readdir } from 'node:fs/promises'
import { fileURLToPath } from 'node:url'
import { join } from 'node:path'
import { parseSkillManifest } from '@gemstack/ai-skills'
import { defineLoop } from '../loop/define.js'
Expand Down Expand Up @@ -51,6 +52,17 @@ export async function loadDomainPreset(dir: string): Promise<DomainPreset> {
})
}

/** Absolute path to the package's shipped `presets/` directory. */
export function builtinPresetsDir(): string {
// From dist/preset/load.js (and dist-test/…), the package root is two up.
return fileURLToPath(new URL('../../presets/', import.meta.url))
}

/** The shipped, stack-agnostic "Software Development" domain preset (#243). */
export function softwareDevelopmentPreset(): Promise<DomainPreset> {
return loadDomainPreset(join(builtinPresetsDir(), 'software-development'))
}

/** Load every `*.md` loop file in a directory (a missing directory yields `[]`). */
export async function loadLoopsFrom(dir: string): Promise<Loop[]> {
const files = await mdFiles(dir)
Expand Down
34 changes: 34 additions & 0 deletions packages/ai-autopilot/src/preset/software-development.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { describe, it } from 'node:test'
import assert from 'node:assert/strict'
import { softwareDevelopmentPreset } from './load.js'

describe('softwareDevelopmentPreset (shipped built-in)', () => {
it('loads the {loops, prompts, skills} bundle from the shipped directory', async () => {
const preset = await softwareDevelopmentPreset()
assert.equal(preset.name, 'software-development')
assert.equal(preset.title, 'Software Development')
assert.ok(preset.description.length > 0)

assert.equal(preset.loops.length, 2)
assert.ok(preset.prompts.length >= 5)
assert.equal(preset.skills.length, 1)
assert.equal(preset.skills[0]!.url, 'https://google.github.io/eng-practices/')
})

it('every id a loop dispatches resolves to a shipped prompt body', async () => {
const preset = await softwareDevelopmentPreset()
const ids = new Set(preset.prompts.map(p => p.id))
for (const loop of preset.loops) {
for (const id of loop.run) {
assert.ok(ids.has(id), `loop prompt "${id}" has a shipped body`)
assert.ok(preset.prompts.find(p => p.id === id)!.instructions.length > 0, `"${id}" body is non-empty`)
}
}
})

it('targets non-web events (major-change, bug-fix)', async () => {
const preset = await softwareDevelopmentPreset()
const kinds = preset.loops.flatMap(l => [...l.on]).sort()
assert.deepEqual(kinds, ['bug-fix', 'major-change'])
})
})
Loading