forgeos 0.1.0-alpha.22 → 0.1.0-alpha.24
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/AGENTS.md +1 -1
- package/CHANGELOG.md +25 -0
- package/LICENSE +21 -0
- package/README.md +2 -2
- package/adapters/java/target/forge-java-adapter-0.1.0-alpha.11.jar +0 -0
- package/adapters/java-spring-boot-starter/target/forge-java-spring-boot-starter-0.1.0-alpha.11.jar +0 -0
- package/docs/cair-protocol.md +103 -0
- package/docs/changelog.md +33 -0
- package/examples/java-billing/target/java-billing-0.1.0-alpha.11-all.jar +0 -0
- package/examples/java-billing/target/java-billing-0.1.0-alpha.11.jar +0 -0
- package/package.json +4 -1
- package/src/forge/_generated/releaseManifest.json +1 -1
- package/src/forge/_generated/releaseManifest.ts +3 -3
- package/src/forge/agent-memory/bridge.ts +16 -0
- package/src/forge/agent-memory/context-pack.ts +28 -0
- package/src/forge/agent-memory/types.ts +6 -0
- package/src/forge/cli/commands.ts +2 -2
- package/src/forge/cli/new.ts +8 -0
- package/src/forge/cli/parse.ts +2 -0
- package/src/forge/compiler/integration/add.ts +1 -1
- package/src/forge/compiler/integration/templates/convex.ts +70 -0
- package/src/forge/compiler/integration/templates/render.ts +3 -0
- package/src/forge/compiler/recipes/definitions.ts +25 -0
- package/src/forge/compiler/recipes/index.ts +1 -0
- package/src/forge/compiler/recipes/registry.ts +3 -0
- package/src/forge/delta/explain.ts +113 -1
- package/src/forge/delta/store.ts +77 -0
- package/src/forge/version.ts +1 -1
package/AGENTS.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// @forge-generated generator=0.1.0-alpha.
|
|
1
|
+
// @forge-generated generator=0.1.0-alpha.24 input=1257771517e723f30d9c8c6bdc328f3a9d0bfde1e673d859837f93f0f1bd15c4 content=0d493cf0e41b71cb652d5e0e1b0c1f83d2a1281b748321f0b00f0773ba93074e
|
|
2
2
|
# AGENTS.md
|
|
3
3
|
|
|
4
4
|
<!-- forge-generated:start -->
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,31 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.1.0-alpha.24
|
|
6
|
+
|
|
7
|
+
### Patch Changes
|
|
8
|
+
|
|
9
|
+
- Consolidate the public alpha adoption surface and agent-contract positioning.
|
|
10
|
+
|
|
11
|
+
- Add an explicit MIT `LICENSE`, package license metadata, and a private GitHub Security Advisory disclosure path.
|
|
12
|
+
- Add stable-alpha, AI-coding, agent demo, Convex, and agent-eval documentation pages, plus a runner-agnostic eval task scaffold.
|
|
13
|
+
- Fix `forge new --json` so scaffold automation receives structured JSON instead of human next-step text.
|
|
14
|
+
- Add the first `forge add convex` app-contract recipe with runtime placement guardrails, optional Convex environment names, generated docs, and a mock testkit.
|
|
15
|
+
- Expand field-report expectations and package/release tests for license, security disclosure, docs, create-app help, Convex recipes, and JSON scaffold output.
|
|
16
|
+
|
|
17
|
+
## 0.1.0-alpha.23
|
|
18
|
+
|
|
19
|
+
### Patch Changes
|
|
20
|
+
|
|
21
|
+
- Tighten the post-alpha.22 release surface and package evidence.
|
|
22
|
+
|
|
23
|
+
- Add a dedicated Nuxt template smoke workflow that installs `nuxt-web`, runs Forge generation/checks, runs Nuxt typecheck, and probes `forge dev --once`.
|
|
24
|
+
- Include `nuxt-web` in the default field-test template matrix.
|
|
25
|
+
- Add explicit `scopeTarget` metadata and human-readable target output for `forge agent context --change`, `--proof`, and `--handoff`.
|
|
26
|
+
- Teach `forge explain` to fall back to the current generated agent contract when DeltaDB has no runtime history, while marking the result as contract-defined instead of executed.
|
|
27
|
+
- Downgrade read-only observation commands such as `forge status`, `forge changed`, `forge handoff`, `forge explain`, `forge timeline`, and CAIR queries to low-confidence context-gathering sessions in DeltaDB.
|
|
28
|
+
- Package `docs/cair-protocol.md` in the npm tarball and expand the public security/threat-model docs for DeltaDB, agent memory, CAIR, Studio bridge, brownfield import, and Nuxt surfaces.
|
|
29
|
+
|
|
5
30
|
## 0.1.0-alpha.22
|
|
6
31
|
|
|
7
32
|
### Patch Changes
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ForgeOS contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# ForgeOS
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The contract layer for agentic software development. ForgeOS turns application source into deterministic runtime contracts, generated clients, safety checks, and machine-readable context that humans and AI coding agents can use safely.
|
|
4
4
|
|
|
5
5
|
**Status:** private/public alpha MVP, implemented through H49. ForgeOS already includes the compiler, local runtime, frontend SDK, production auth, RLS compiler, liveQuery, self-host artifacts, generated agent contract, guided dev loop, repair/review/test tooling, AST-aware codemods, package intelligence, native AI tools/agents, DeltaDB work memory, external agent memory ingestion, brownfield import analysis, npm alpha publishing, and Read the Docs public docs. Public release hardening is still focused on deeper semantic codemods, broader field reports, and more production mileage.
|
|
6
6
|
|
|
@@ -8,7 +8,7 @@ Public docs live at [forgeos.readthedocs.io](https://forgeos.readthedocs.io/). T
|
|
|
8
8
|
|
|
9
9
|
Start with [Why ForgeOS](https://forgeos.readthedocs.io/en/latest/why-forgeos/) to understand the agent-native design.
|
|
10
10
|
|
|
11
|
-
For the short version, read [The Five-Minute ForgeOS Model](https://forgeos.readthedocs.io/en/latest/five-minute-model/)
|
|
11
|
+
For the short version, read [The Five-Minute ForgeOS Model](https://forgeos.readthedocs.io/en/latest/five-minute-model/), [Alpha Golden Path](https://forgeos.readthedocs.io/en/latest/golden-path/), and [Stable Alpha Surface](https://forgeos.readthedocs.io/en/latest/stable-alpha/). The important distinction is that external coding agents are the primary ForgeOS workflow; integrated AI SDK features are available for apps that need in-product AI, but they are not the main development loop.
|
|
12
12
|
|
|
13
13
|
## Agent-First Quickstart
|
|
14
14
|
|
|
Binary file
|
package/adapters/java-spring-boot-starter/target/forge-java-spring-boot-starter-0.1.0-alpha.11.jar
CHANGED
|
Binary file
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# CAIR Protocol
|
|
2
|
+
|
|
3
|
+
CAIR is ForgeOS' compact agent interface for reading code structure, querying symbols, and planning safe edits without loading the whole repository into an agent context window.
|
|
4
|
+
|
|
5
|
+
CAIR is intentionally CLI-first. It does not add a second mutating tool surface. Agents should use CAIR to inspect structure and create reviewable plans, then apply those plans through the ForgeOS CLI with hash checks and rollback journals.
|
|
6
|
+
|
|
7
|
+
## Read Workflow
|
|
8
|
+
|
|
9
|
+
Start with a compact snapshot:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
forge cair snapshot
|
|
13
|
+
forge cair query "Q STATUS"
|
|
14
|
+
forge cair query "Q ST"
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Use symbol, definition, reference, impact, and dependency API queries before opening large files:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
forge cair query "Q S name=createTicket"
|
|
21
|
+
forge cair query "Q D S#1"
|
|
22
|
+
forge cair query "Q R S#1"
|
|
23
|
+
forge cair query "Q I S#1"
|
|
24
|
+
forge cair query "Q DEP.API package=zod symbol=object"
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
The goal is to make agent navigation evidence-backed: CAIR gives stable ids for modules, symbols, packages, APIs, and tests so an agent can select a small file set instead of scanning the whole project.
|
|
28
|
+
|
|
29
|
+
## Action Safety
|
|
30
|
+
|
|
31
|
+
Mutating CAIR actions should be planned first:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
forge cair action --plan "A RN t=S#1 nn=openTicket"
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
The plan lives under `.forge/cair/plans/` and records the target files and expected hashes. Applying a plan checks those hashes before editing:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
forge cair action "A APPLY plan=<P#|.forge/cair/plans/...json>"
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Applied plans write rollback journals under `.forge/cair/journal/`:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
forge cair action "A ROLLBACK journal=.forge/cair/journal/<journal>.json"
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Use `--dry-run` when exploring an action shape without creating a plan:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
forge cair action --dry-run "A CREATE.SYMBOL path=src/example.ts kind=function name=example export=true createFile=true"
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Generated files stay protected by default. Use `--include-generated` only when the edit is intentionally about generated artifacts.
|
|
56
|
+
|
|
57
|
+
## DeltaDB Evidence
|
|
58
|
+
|
|
59
|
+
Successful CAIR CLI runs are recorded in DeltaDB as sanitized operational events:
|
|
60
|
+
|
|
61
|
+
| CAIR command | Delta event |
|
|
62
|
+
|--------------|-------------|
|
|
63
|
+
| `forge cair snapshot` | `cair.snapshot.created` |
|
|
64
|
+
| `forge cair query ...` | `cair.query.run` |
|
|
65
|
+
| `forge cair action --plan ...` | `cair.plan.created` |
|
|
66
|
+
| `forge cair action "A APPLY ..."` | `cair.plan.applied` |
|
|
67
|
+
| `forge cair action --dry-run ...` | `cair.action.previewed` |
|
|
68
|
+
|
|
69
|
+
The recorder stores compact verbs such as `Q ST` or `A APPLY`; it does not need to store full action bodies as first-class timeline data. Use:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
forge timeline cair:protocol --json
|
|
73
|
+
forge timeline --kind cair.plan.applied --json
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
This makes CAIR navigation and guarded edits visible beside file changes, proofs, and agent activity.
|
|
77
|
+
|
|
78
|
+
## Compact Aliases
|
|
79
|
+
|
|
80
|
+
| Long form | Compact |
|
|
81
|
+
|-----------|---------|
|
|
82
|
+
| `Q STATUS` | `Q ST` |
|
|
83
|
+
| `Q SYMBOL` | `Q S` |
|
|
84
|
+
| `Q DEF` | `Q D` |
|
|
85
|
+
| `Q REFS` | `Q R` |
|
|
86
|
+
| `Q IMPACT` | `Q I` |
|
|
87
|
+
| `A RENAME.SYMBOL target=S#1 newName=x` | `A RN t=S#1 nn=x` |
|
|
88
|
+
|
|
89
|
+
## Agent Posture
|
|
90
|
+
|
|
91
|
+
Use this loop for code work:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
forge cair snapshot
|
|
95
|
+
forge cair query "Q ST"
|
|
96
|
+
forge cair query "Q S name=<symbol>"
|
|
97
|
+
forge cair query "Q I S#1"
|
|
98
|
+
forge cair action --plan "A RN t=S#1 nn=<newName>"
|
|
99
|
+
forge cair action "A APPLY plan=<P#|path>"
|
|
100
|
+
forge check --json
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Do not use CAIR as a bypass around ForgeOS rules. Commands remain transactional writes, queries and liveQueries remain read-only, side effects still belong in actions and workflows, and generated artifacts remain derived.
|
package/docs/changelog.md
CHANGED
|
@@ -6,6 +6,39 @@ The canonical source file in the repository is `CHANGELOG.md`.
|
|
|
6
6
|
|
|
7
7
|
## Unreleased
|
|
8
8
|
|
|
9
|
+
## 0.1.0-alpha.24
|
|
10
|
+
|
|
11
|
+
- Consolidated the public alpha adoption surface: MIT license, package license
|
|
12
|
+
metadata, private GitHub Security Advisory disclosure, stable-alpha status
|
|
13
|
+
matrix, AI-coding loop, three short agent demos, and repeatable agent eval
|
|
14
|
+
task specs.
|
|
15
|
+
- `forge new --json` now returns structured JSON for scaffold automation
|
|
16
|
+
instead of human next-step text.
|
|
17
|
+
- Added the initial `forge add convex` app-contract recipe with runtime
|
|
18
|
+
placement guardrails, optional Convex environment names, generated docs, and
|
|
19
|
+
a mock testkit. The deeper Convex schema/API importer is documented as
|
|
20
|
+
planned rather than implied.
|
|
21
|
+
- Expanded field-report expectations and release/doc tests for license,
|
|
22
|
+
security disclosure, create-app help, Convex recipes, and public docs
|
|
23
|
+
navigation.
|
|
24
|
+
|
|
25
|
+
## 0.1.0-alpha.23
|
|
26
|
+
|
|
27
|
+
- Tightened the post-alpha.22 release surface and package evidence:
|
|
28
|
+
added a dedicated Nuxt template smoke workflow, included `nuxt-web` in the
|
|
29
|
+
default field-test template matrix, packaged `docs/cair-protocol.md`, and
|
|
30
|
+
expanded the security/threat-model docs for DeltaDB, agent memory, CAIR,
|
|
31
|
+
Studio bridge, brownfield import, and Nuxt surfaces.
|
|
32
|
+
- `forge agent context` now returns explicit `scopeTarget` metadata and prints
|
|
33
|
+
the resolved context target for entry, change, proof, and handoff packs.
|
|
34
|
+
- `forge explain` now falls back to the current generated agent contract when
|
|
35
|
+
DeltaDB has no runtime history, marking the entry as contract-defined rather
|
|
36
|
+
than executed.
|
|
37
|
+
- DeltaDB work-session inference now treats read-only observation commands such
|
|
38
|
+
as `forge status`, `forge changed`, `forge handoff`, `forge explain`,
|
|
39
|
+
`forge timeline`, and CAIR queries as low-confidence context-gathering
|
|
40
|
+
sessions.
|
|
41
|
+
|
|
9
42
|
## 0.1.0-alpha.22
|
|
10
43
|
|
|
11
44
|
- Added focused post-alpha.21 workflow improvements without expanding MCP tools:
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "forgeos",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.24",
|
|
4
4
|
"description": "Agent-native application framework and compiler for building Forge apps without a mandatory dashboard.",
|
|
5
5
|
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
6
7
|
"files": [
|
|
7
8
|
"adapters/",
|
|
8
9
|
"bin/",
|
|
10
|
+
"docs/cair-protocol.md",
|
|
9
11
|
"docs/forge-protocol.md",
|
|
10
12
|
"examples/go-billing/",
|
|
11
13
|
"examples/java-billing/",
|
|
@@ -17,6 +19,7 @@
|
|
|
17
19
|
"src/forge/_generated/releaseManifest.ts",
|
|
18
20
|
"templates/",
|
|
19
21
|
"README.md",
|
|
22
|
+
"LICENSE",
|
|
20
23
|
"CHANGELOG.md",
|
|
21
24
|
"CONTRIBUTING.md",
|
|
22
25
|
"AGENTS.md",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"defaultProvider":"local","diagnostics":[],"env":{"deployEnv":"FORGE_DEPLOY_ENV","deployId":"FORGE_DEPLOY_ID","publicReleaseId":"NEXT_PUBLIC_FORGE_RELEASE_ID","releaseId":"FORGE_RELEASE_ID"},"gitSha":"unknown","optionalProviders":["local","sentry-compatible","sentry","glitchtip","bugsink","otel","custom"],"packageName":"forgeos","packageVersion":"0.1.0-alpha.
|
|
1
|
+
{"defaultProvider":"local","diagnostics":[],"env":{"deployEnv":"FORGE_DEPLOY_ENV","deployId":"FORGE_DEPLOY_ID","publicReleaseId":"NEXT_PUBLIC_FORGE_RELEASE_ID","releaseId":"FORGE_RELEASE_ID"},"gitSha":"unknown","optionalProviders":["local","sentry-compatible","sentry","glitchtip","bugsink","otel","custom"],"packageName":"forgeos","packageVersion":"0.1.0-alpha.24","releaseId":"forgeos@0.1.0-alpha.24+unknown","schemaVersion":"0.1.0"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// @forge-generated generator=0.1.0-alpha.
|
|
1
|
+
// @forge-generated generator=0.1.0-alpha.24 input=1257771517e723f30d9c8c6bdc328f3a9d0bfde1e673d859837f93f0f1bd15c4 content=4170e33ff26cbaa55732ed4f507f83f2ece3812a1c2184614337c8be91110499
|
|
2
2
|
export const releaseManifest = {
|
|
3
3
|
"defaultProvider": "local",
|
|
4
4
|
"diagnostics": [],
|
|
@@ -19,7 +19,7 @@ export const releaseManifest = {
|
|
|
19
19
|
"custom"
|
|
20
20
|
],
|
|
21
21
|
"packageName": "forgeos",
|
|
22
|
-
"packageVersion": "0.1.0-alpha.
|
|
23
|
-
"releaseId": "forgeos@0.1.0-alpha.
|
|
22
|
+
"packageVersion": "0.1.0-alpha.24",
|
|
23
|
+
"releaseId": "forgeos@0.1.0-alpha.24+unknown",
|
|
24
24
|
"schemaVersion": "0.1.0"
|
|
25
25
|
} as const;
|
|
@@ -1021,6 +1021,7 @@ function formatAgentMemoryContextHuman(result: AgentMemoryContextPack): string {
|
|
|
1021
1021
|
const lines = [
|
|
1022
1022
|
`Forge Agent Context (${result.scope}${result.entry ? `: ${result.entry}` : ""})`,
|
|
1023
1023
|
"",
|
|
1024
|
+
`target: ${formatAgentContextTarget(result)}`,
|
|
1024
1025
|
`events: ${summary.events}`,
|
|
1025
1026
|
`sources: ${summary.sources.length > 0 ? summary.sources.join(", ") : "none"}`,
|
|
1026
1027
|
`tools: ${summary.tools.length > 0 ? summary.tools.join(", ") : "none"}`,
|
|
@@ -1067,6 +1068,21 @@ function formatAgentMemoryContextHuman(result: AgentMemoryContextPack): string {
|
|
|
1067
1068
|
return `${lines.join("\n")}\n`;
|
|
1068
1069
|
}
|
|
1069
1070
|
|
|
1071
|
+
function formatAgentContextTarget(result: AgentMemoryContextPack): string {
|
|
1072
|
+
const target = result.scopeTarget;
|
|
1073
|
+
const parts: string[] = [target.kind];
|
|
1074
|
+
if (target.value) {
|
|
1075
|
+
parts.push(target.value);
|
|
1076
|
+
}
|
|
1077
|
+
if (target.semanticTarget && target.semanticTarget !== target.value) {
|
|
1078
|
+
parts.push(`semantic=${target.semanticTarget}`);
|
|
1079
|
+
}
|
|
1080
|
+
if (target.currentSessionId) {
|
|
1081
|
+
parts.push(`session=${target.currentSessionId}`);
|
|
1082
|
+
}
|
|
1083
|
+
return parts.join(" ");
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1070
1086
|
function formatAgentMemoryEventsHuman(events: AgentMemoryEventRecord[]): string {
|
|
1071
1087
|
const sources = uniqueStrings(events.map((event) => event.sourceName));
|
|
1072
1088
|
const tools = uniqueStrings(events.flatMap((event) => {
|
|
@@ -48,6 +48,7 @@ export async function buildAgentMemoryContext(input: {
|
|
|
48
48
|
return {
|
|
49
49
|
ok: true,
|
|
50
50
|
scope,
|
|
51
|
+
scopeTarget: contextScopeTarget(input, scope, target, currentSession?.id),
|
|
51
52
|
entry: input.entry,
|
|
52
53
|
change: input.change,
|
|
53
54
|
proof: input.proof,
|
|
@@ -117,6 +118,33 @@ function contextTarget(
|
|
|
117
118
|
return undefined;
|
|
118
119
|
}
|
|
119
120
|
|
|
121
|
+
function contextScopeTarget(
|
|
122
|
+
input: { entry?: string; change?: string; proof?: string; handoff?: boolean },
|
|
123
|
+
scope: AgentMemoryContextPack["scope"],
|
|
124
|
+
semanticTarget: string | undefined,
|
|
125
|
+
currentSessionId: string | undefined,
|
|
126
|
+
): AgentMemoryContextPack["scopeTarget"] {
|
|
127
|
+
if (scope === "entry") {
|
|
128
|
+
return { kind: "entry", value: input.entry, semanticTarget };
|
|
129
|
+
}
|
|
130
|
+
if (scope === "proof") {
|
|
131
|
+
return { kind: "proof", value: input.proof, semanticTarget };
|
|
132
|
+
}
|
|
133
|
+
if (scope === "change") {
|
|
134
|
+
const value = input.change ?? "current";
|
|
135
|
+
return {
|
|
136
|
+
kind: "change",
|
|
137
|
+
value,
|
|
138
|
+
...(semanticTarget ? { semanticTarget } : {}),
|
|
139
|
+
...(value === "current" && currentSessionId ? { currentSessionId } : {}),
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
if (scope === "handoff") {
|
|
143
|
+
return { kind: "handoff", value: "handoff", currentSessionId };
|
|
144
|
+
}
|
|
145
|
+
return { kind: "current-session", value: "current", currentSessionId };
|
|
146
|
+
}
|
|
147
|
+
|
|
120
148
|
function eventTarget(
|
|
121
149
|
input: { entry?: string; change?: string; proof?: string },
|
|
122
150
|
scope: AgentMemoryContextPack["scope"],
|
|
@@ -93,6 +93,12 @@ export interface AgentMemoryContextEvent {
|
|
|
93
93
|
export interface AgentMemoryContextPack {
|
|
94
94
|
ok: true;
|
|
95
95
|
scope: "current" | "entry" | "change" | "proof" | "handoff";
|
|
96
|
+
scopeTarget: {
|
|
97
|
+
kind: "current-session" | "entry" | "change" | "proof" | "handoff";
|
|
98
|
+
value?: string;
|
|
99
|
+
semanticTarget?: string;
|
|
100
|
+
currentSessionId?: string;
|
|
101
|
+
};
|
|
96
102
|
entry?: string;
|
|
97
103
|
change?: string;
|
|
98
104
|
proof?: string;
|
|
@@ -168,7 +168,7 @@ import {
|
|
|
168
168
|
runSecretsCommand,
|
|
169
169
|
} from "./secrets.ts";
|
|
170
170
|
import { formatAiHuman, formatAiJson, runAiCommand } from "./ai.ts";
|
|
171
|
-
import { formatNewHuman, runNewCommand } from "./new.ts";
|
|
171
|
+
import { formatNewHuman, formatNewJson, runNewCommand } from "./new.ts";
|
|
172
172
|
import { formatBuildHuman, runBuildCommand } from "./build.ts";
|
|
173
173
|
import { runServeCommand } from "./serve.ts";
|
|
174
174
|
import { runWorkerCommand } from "./worker.ts";
|
|
@@ -1559,7 +1559,7 @@ export async function executeCommand(command: ForgeCommand): Promise<number> {
|
|
|
1559
1559
|
localForge: command.localForge,
|
|
1560
1560
|
workspaceRoot: command.workspaceRoot,
|
|
1561
1561
|
});
|
|
1562
|
-
process.stdout.write(formatNewHuman(result));
|
|
1562
|
+
process.stdout.write(command.json ? formatNewJson(result) : formatNewHuman(result));
|
|
1563
1563
|
return result.exitCode;
|
|
1564
1564
|
}
|
|
1565
1565
|
case "build": {
|
package/src/forge/cli/new.ts
CHANGED
|
@@ -535,3 +535,11 @@ export function formatNewHuman(result: NewCommandResult): string {
|
|
|
535
535
|
"",
|
|
536
536
|
].join("\n");
|
|
537
537
|
}
|
|
538
|
+
|
|
539
|
+
export function formatNewJson(result: NewCommandResult): string {
|
|
540
|
+
return `${JSON.stringify({
|
|
541
|
+
schemaVersion: "0.1.0",
|
|
542
|
+
ok: result.exitCode === 0,
|
|
543
|
+
...result,
|
|
544
|
+
}, null, 2)}\n`;
|
|
545
|
+
}
|
package/src/forge/cli/parse.ts
CHANGED
|
@@ -63,6 +63,7 @@ export type ForgeCommand =
|
|
|
63
63
|
git: boolean;
|
|
64
64
|
forgePackageSpec?: string;
|
|
65
65
|
localForge: boolean;
|
|
66
|
+
json: boolean;
|
|
66
67
|
workspaceRoot: string;
|
|
67
68
|
}
|
|
68
69
|
| { kind: "build"; json: boolean; workspaceRoot: string }
|
|
@@ -860,6 +861,7 @@ export function parseCli(argv: string[]): ParsedCli {
|
|
|
860
861
|
git: !parseFlag(argv, "--no-git"),
|
|
861
862
|
forgePackageSpec,
|
|
862
863
|
localForge,
|
|
864
|
+
json: parseFlag(argv, "--json"),
|
|
863
865
|
workspaceRoot,
|
|
864
866
|
},
|
|
865
867
|
workspaceRoot,
|
|
@@ -549,7 +549,7 @@ export async function forgeAdd(
|
|
|
549
549
|
const error = createDiagnostic({
|
|
550
550
|
severity: "error",
|
|
551
551
|
code: "FORGE_UNKNOWN_ALIAS",
|
|
552
|
-
message: `unknown integration alias '${alias}'; supported: stripe, posthog, sentry, zod, ai. For npm packages, use 'forge add package ${alias}' or 'forge add ${alias}'.`,
|
|
552
|
+
message: `unknown integration alias '${alias}'; supported: stripe, posthog, sentry, zod, convex, ai. For npm packages, use 'forge add package ${alias}' or 'forge add ${alias}'.`,
|
|
553
553
|
suggestedCommands: [`forge add package ${alias} --dry-run --json`, "forge add --help"],
|
|
554
554
|
});
|
|
555
555
|
return finalizeAddResult({
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { IntegrationTemplateInput } from "./types.ts";
|
|
2
|
+
|
|
3
|
+
export function renderConvexTestkit(_input: IntegrationTemplateInput): string {
|
|
4
|
+
return [
|
|
5
|
+
"/** Forge generated mock testkit for Convex app-contract evaluation. */",
|
|
6
|
+
"export interface ConvexFunctionRef {",
|
|
7
|
+
" readonly kind: \"query\" | \"mutation\" | \"action\";",
|
|
8
|
+
" readonly path: string;",
|
|
9
|
+
"}",
|
|
10
|
+
"",
|
|
11
|
+
"export function createConvexMock(functions: ConvexFunctionRef[] = []) {",
|
|
12
|
+
" const calls: Array<{ path: string; args: unknown }> = [];",
|
|
13
|
+
" return {",
|
|
14
|
+
" functions,",
|
|
15
|
+
" calls,",
|
|
16
|
+
" call(path: string, args: unknown = {}) {",
|
|
17
|
+
" calls.push({ path, args });",
|
|
18
|
+
" return { ok: true, path, args } as const;",
|
|
19
|
+
" },",
|
|
20
|
+
" } as const;",
|
|
21
|
+
"}",
|
|
22
|
+
"",
|
|
23
|
+
].join("\n");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function renderConvexDoc(input: IntegrationTemplateInput): string {
|
|
27
|
+
return [
|
|
28
|
+
"# Convex integration",
|
|
29
|
+
"",
|
|
30
|
+
"Generated by Forge (recipe v1).",
|
|
31
|
+
"",
|
|
32
|
+
"Convex is treated as an agent-friendly backend package. ForgeOS treats it as an app-contract integration surface: package placement, frontend/backend usage, generated docs, and future function-map import.",
|
|
33
|
+
"",
|
|
34
|
+
"## Runtime placement",
|
|
35
|
+
"",
|
|
36
|
+
"- Use Convex client hooks and generated Convex APIs in frontend/client code.",
|
|
37
|
+
"- Use Convex server/admin clients only from server, action, workflow, or endpoint contexts.",
|
|
38
|
+
"- Do not import Convex runtime clients from Forge commands, queries, or liveQueries.",
|
|
39
|
+
"- Type-only imports are acceptable when the TypeScript compiler erases them.",
|
|
40
|
+
"",
|
|
41
|
+
"## App-contract import targets",
|
|
42
|
+
"",
|
|
43
|
+
"Future Convex import support should inspect:",
|
|
44
|
+
"",
|
|
45
|
+
"- `convex/schema.ts` for table names and fields;",
|
|
46
|
+
"- `convex/_generated/api.d.ts` for query, mutation, and action references;",
|
|
47
|
+
"- frontend `useQuery`, `useMutation`, and `useAction` calls for route/component capability mapping;",
|
|
48
|
+
"- auth and deployment notes from Convex config files.",
|
|
49
|
+
"",
|
|
50
|
+
"## Commands",
|
|
51
|
+
"",
|
|
52
|
+
"```bash",
|
|
53
|
+
"forge add convex",
|
|
54
|
+
"forge deps inspect convex --json",
|
|
55
|
+
"forge inspect runtime-matrix --json",
|
|
56
|
+
"forge check --json",
|
|
57
|
+
"```",
|
|
58
|
+
"",
|
|
59
|
+
"## Generated evidence",
|
|
60
|
+
"",
|
|
61
|
+
`Compatible contexts: ${input.compatible.join(", ")}`,
|
|
62
|
+
"",
|
|
63
|
+
`Incompatible contexts: ${input.incompatible.join(", ")}`,
|
|
64
|
+
"",
|
|
65
|
+
"Optional environment names:",
|
|
66
|
+
"",
|
|
67
|
+
...input.secrets.map((secret) => `- ${secret.envVar}`),
|
|
68
|
+
"",
|
|
69
|
+
].join("\n");
|
|
70
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { IntegrationTemplateInput } from "./types.ts";
|
|
2
2
|
import * as ai from "./ai.ts";
|
|
3
|
+
import * as convex from "./convex.ts";
|
|
3
4
|
import * as posthog from "./posthog.ts";
|
|
4
5
|
import * as sentry from "./sentry.ts";
|
|
5
6
|
import * as stripe from "./stripe.ts";
|
|
@@ -52,6 +53,7 @@ const TESTKIT_RENDERERS: Record<string, Record<string, TemplateRenderer>> = {
|
|
|
52
53
|
stripe: { "stripe.mock.ts": stripe.renderStripeTestkit },
|
|
53
54
|
posthog: { "posthog.mock.ts": posthog.renderPosthogTestkit },
|
|
54
55
|
sentry: { "sentry.mock.ts": sentry.renderSentryTestkit },
|
|
56
|
+
convex: { "convex.mock.ts": convex.renderConvexTestkit },
|
|
55
57
|
ai: { "ai.mock.ts": ai.renderAiTestkitLegacy },
|
|
56
58
|
};
|
|
57
59
|
|
|
@@ -60,6 +62,7 @@ const DOC_RENDERERS: Record<string, Record<string, TemplateRenderer>> = {
|
|
|
60
62
|
stripe: { "stripe.md": stripe.renderStripeDoc },
|
|
61
63
|
posthog: { "posthog.md": posthog.renderPosthogDoc },
|
|
62
64
|
sentry: { "sentry.md": sentry.renderSentryDoc },
|
|
65
|
+
convex: { "convex.md": convex.renderConvexDoc },
|
|
63
66
|
ai: { "ai.md": ai.renderAiDoc },
|
|
64
67
|
};
|
|
65
68
|
|
|
@@ -190,6 +190,30 @@ export const ZOD_RECIPE: IntegrationRecipe = {
|
|
|
190
190
|
docs: ["zod.md"],
|
|
191
191
|
};
|
|
192
192
|
|
|
193
|
+
export const CONVEX_RECIPE: IntegrationRecipe = {
|
|
194
|
+
alias: "convex",
|
|
195
|
+
packages: [{ packageName: "convex" }],
|
|
196
|
+
supportedVersionRange: ">=1.0.0",
|
|
197
|
+
recipeVersion: "1.0.0",
|
|
198
|
+
contexts: {
|
|
199
|
+
allowed: ["client", "server", "action", "workflow", "endpoint", "test", "build"],
|
|
200
|
+
denied: ["shared", "query", "liveQuery", "command", "edge"],
|
|
201
|
+
},
|
|
202
|
+
capabilities: networkCapability(
|
|
203
|
+
["*.convex.cloud", "*.convex.site"],
|
|
204
|
+
["recipe:convex", "Convex client/server package connects to Convex deployments"],
|
|
205
|
+
),
|
|
206
|
+
secrets: [
|
|
207
|
+
secret("NEXT_PUBLIC_CONVEX_URL", false),
|
|
208
|
+
secret("CONVEX_URL", false),
|
|
209
|
+
secret("CONVEX_DEPLOYMENT", false),
|
|
210
|
+
secret("CONVEX_DEPLOY_KEY", false),
|
|
211
|
+
],
|
|
212
|
+
adapters: [],
|
|
213
|
+
testkits: ["convex.mock.ts"],
|
|
214
|
+
docs: ["convex.md"],
|
|
215
|
+
};
|
|
216
|
+
|
|
193
217
|
export const FORGE_RECIPE: IntegrationRecipe = {
|
|
194
218
|
alias: "forge",
|
|
195
219
|
packages: [{ packageName: "forge" }],
|
|
@@ -283,6 +307,7 @@ export const REFERENCE_ALIASES = [
|
|
|
283
307
|
"posthog",
|
|
284
308
|
"sentry",
|
|
285
309
|
"zod",
|
|
310
|
+
"convex",
|
|
286
311
|
"ai",
|
|
287
312
|
] as const;
|
|
288
313
|
|
|
@@ -3,6 +3,7 @@ import type { PackageRecipe } from "../types/integration.ts";
|
|
|
3
3
|
import {
|
|
4
4
|
AI_PROVIDER_RECIPES,
|
|
5
5
|
AI_RECIPE,
|
|
6
|
+
CONVEX_RECIPE,
|
|
6
7
|
FORGE_RECIPE,
|
|
7
8
|
POSTHOG_RECIPE,
|
|
8
9
|
REFERENCE_ALIASES,
|
|
@@ -23,6 +24,7 @@ const REFERENCE_RECIPES: Record<string, IntegrationRecipe> = {
|
|
|
23
24
|
posthog: POSTHOG_RECIPE,
|
|
24
25
|
sentry: SENTRY_RECIPE,
|
|
25
26
|
zod: ZOD_RECIPE,
|
|
27
|
+
convex: CONVEX_RECIPE,
|
|
26
28
|
forge: FORGE_RECIPE,
|
|
27
29
|
ai: AI_RECIPE,
|
|
28
30
|
};
|
|
@@ -32,6 +34,7 @@ const ALL_RECIPES: IntegrationRecipe[] = [
|
|
|
32
34
|
POSTHOG_RECIPE,
|
|
33
35
|
SENTRY_RECIPE,
|
|
34
36
|
ZOD_RECIPE,
|
|
37
|
+
CONVEX_RECIPE,
|
|
35
38
|
FORGE_RECIPE,
|
|
36
39
|
AI_RECIPE,
|
|
37
40
|
...AI_PROVIDER_RECIPES,
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
1
3
|
import { DeltaStore } from "./store.ts";
|
|
2
4
|
|
|
3
5
|
export interface DeltaExplainResult {
|
|
@@ -13,10 +15,11 @@ export async function runDeltaExplain(input: {
|
|
|
13
15
|
}): Promise<DeltaExplainResult> {
|
|
14
16
|
const store = await DeltaStore.open(input.workspaceRoot, { access: "read" });
|
|
15
17
|
try {
|
|
18
|
+
const explanation = await store.explain(input.thing);
|
|
16
19
|
return {
|
|
17
20
|
ok: true,
|
|
18
21
|
thing: input.thing,
|
|
19
|
-
explanation:
|
|
22
|
+
explanation: enrichWithCurrentAgentContract(input.workspaceRoot, input.thing, explanation),
|
|
20
23
|
exitCode: 0,
|
|
21
24
|
};
|
|
22
25
|
} finally {
|
|
@@ -68,6 +71,9 @@ export function formatDeltaExplainHuman(result: DeltaExplainResult): string {
|
|
|
68
71
|
lines.push("Runtime:");
|
|
69
72
|
lines.push(` kind: ${String(runtime.entry_kind ?? "unknown")}`);
|
|
70
73
|
lines.push(` result: ${String(runtime.result ?? "unknown")}`);
|
|
74
|
+
if (runtime.source) {
|
|
75
|
+
lines.push(` source: ${String(runtime.source)}`);
|
|
76
|
+
}
|
|
71
77
|
if (runtime.diagnostic_code) {
|
|
72
78
|
lines.push(` diagnostic: ${String(runtime.diagnostic_code)}`);
|
|
73
79
|
}
|
|
@@ -94,6 +100,22 @@ export function formatDeltaExplainHuman(result: DeltaExplainResult): string {
|
|
|
94
100
|
}
|
|
95
101
|
}
|
|
96
102
|
}
|
|
103
|
+
const currentContract = explanation.currentContract as Record<string, unknown> | null | undefined;
|
|
104
|
+
if (currentContract) {
|
|
105
|
+
lines.push("");
|
|
106
|
+
lines.push("Current contract:");
|
|
107
|
+
lines.push(` kind: ${String(currentContract.kind ?? "unknown")}`);
|
|
108
|
+
lines.push(` name: ${String(currentContract.name ?? result.thing)}`);
|
|
109
|
+
if (currentContract.auth) {
|
|
110
|
+
lines.push(` auth: ${String(currentContract.auth)}`);
|
|
111
|
+
}
|
|
112
|
+
if (currentContract.policy) {
|
|
113
|
+
lines.push(` policy: ${String(currentContract.policy)}`);
|
|
114
|
+
}
|
|
115
|
+
if (currentContract.sourceFile) {
|
|
116
|
+
lines.push(` file: ${String(currentContract.sourceFile)}`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
97
119
|
lines.push("");
|
|
98
120
|
lines.push("Introduced in:");
|
|
99
121
|
if (workSessions.length === 0) {
|
|
@@ -124,3 +146,93 @@ export function formatDeltaExplainHuman(result: DeltaExplainResult): string {
|
|
|
124
146
|
export function formatDeltaExplainJson(result: DeltaExplainResult): string {
|
|
125
147
|
return `${JSON.stringify(result, null, 2)}\n`;
|
|
126
148
|
}
|
|
149
|
+
|
|
150
|
+
function enrichWithCurrentAgentContract(
|
|
151
|
+
workspaceRoot: string,
|
|
152
|
+
thing: string,
|
|
153
|
+
explanation: Record<string, unknown>,
|
|
154
|
+
): Record<string, unknown> {
|
|
155
|
+
const currentContract = currentContractForThing(workspaceRoot, thing);
|
|
156
|
+
if (!currentContract) {
|
|
157
|
+
return explanation;
|
|
158
|
+
}
|
|
159
|
+
const runtime = explanation.runtime && typeof explanation.runtime === "object"
|
|
160
|
+
? explanation.runtime as Record<string, unknown>
|
|
161
|
+
: null;
|
|
162
|
+
return {
|
|
163
|
+
...explanation,
|
|
164
|
+
type: explanation.type === "unknown" ? "runtime-entry" : explanation.type,
|
|
165
|
+
runtime: runtime ?? {
|
|
166
|
+
entry_name: currentContract.name,
|
|
167
|
+
entry_kind: currentContract.kind,
|
|
168
|
+
result: "defined",
|
|
169
|
+
source: "agentContract",
|
|
170
|
+
...(currentContract.policy ? { policy: currentContract.policy } : {}),
|
|
171
|
+
...(typeof currentContract.tenantScoped === "boolean" ? { tenant_scoped: currentContract.tenantScoped } : {}),
|
|
172
|
+
...(typeof currentContract.needsApproval === "boolean" ? { needs_approval: currentContract.needsApproval } : {}),
|
|
173
|
+
},
|
|
174
|
+
currentContract,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function currentContractForThing(workspaceRoot: string, thing: string): Record<string, unknown> | undefined {
|
|
179
|
+
const contractPath = join(workspaceRoot, "src", "forge", "_generated", "agentContract.json");
|
|
180
|
+
if (!existsSync(contractPath)) {
|
|
181
|
+
return undefined;
|
|
182
|
+
}
|
|
183
|
+
try {
|
|
184
|
+
const contract = JSON.parse(readFileSync(contractPath, "utf8")) as Record<string, unknown>;
|
|
185
|
+
const collections: Array<[keyof typeof runtimeKinds, string]> = [
|
|
186
|
+
["commands", "command"],
|
|
187
|
+
["queries", "query"],
|
|
188
|
+
["liveQueries", "liveQuery"],
|
|
189
|
+
["actions", "action"],
|
|
190
|
+
["workflows", "workflow"],
|
|
191
|
+
];
|
|
192
|
+
for (const [collection, kind] of collections) {
|
|
193
|
+
const entries = Array.isArray(contract[collection]) ? contract[collection] as Record<string, unknown>[] : [];
|
|
194
|
+
for (const entry of entries) {
|
|
195
|
+
const name = runtimeEntryName(entry);
|
|
196
|
+
if (!name) {
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
if (name === thing || `${kind}:${name}` === thing || entry.id === thing || entry.exportName === thing) {
|
|
200
|
+
return {
|
|
201
|
+
source: "src/forge/_generated/agentContract.json",
|
|
202
|
+
kind,
|
|
203
|
+
name,
|
|
204
|
+
...(stringValue(entry.auth) ? { auth: stringValue(entry.auth) } : {}),
|
|
205
|
+
...(stringValue(entry.policy) ? { policy: stringValue(entry.policy) } : {}),
|
|
206
|
+
...(typeof entry.tenantScoped === "boolean" ? { tenantScoped: entry.tenantScoped } : {}),
|
|
207
|
+
...(typeof entry.needsApproval === "boolean" ? { needsApproval: entry.needsApproval } : {}),
|
|
208
|
+
...(stringValue(entry.risk) ? { risk: stringValue(entry.risk) } : {}),
|
|
209
|
+
...(stringValue(entry.file) ? { sourceFile: stringValue(entry.file) } : {}),
|
|
210
|
+
...(stringValue(entry.path) ? { sourceFile: stringValue(entry.path) } : {}),
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
} catch {
|
|
216
|
+
return undefined;
|
|
217
|
+
}
|
|
218
|
+
return undefined;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const runtimeKinds = {
|
|
222
|
+
commands: "command",
|
|
223
|
+
queries: "query",
|
|
224
|
+
liveQueries: "liveQuery",
|
|
225
|
+
actions: "action",
|
|
226
|
+
workflows: "workflow",
|
|
227
|
+
} as const;
|
|
228
|
+
|
|
229
|
+
function runtimeEntryName(entry: Record<string, unknown>): string | undefined {
|
|
230
|
+
return stringValue(entry.name)
|
|
231
|
+
?? stringValue(entry.exportName)
|
|
232
|
+
?? stringValue(entry.id)
|
|
233
|
+
?? stringValue(entry.entryName);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function stringValue(value: unknown): string | undefined {
|
|
237
|
+
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
238
|
+
}
|
package/src/forge/delta/store.ts
CHANGED
|
@@ -2809,6 +2809,12 @@ function scoreWorkSessionCandidate(
|
|
|
2809
2809
|
): { score: number; signals: DeltaWorkSessionSignal[] } {
|
|
2810
2810
|
const signals: DeltaWorkSessionSignal[] = [];
|
|
2811
2811
|
const metadata = candidate.metadata;
|
|
2812
|
+
if (isObservationOnlyContext(context) && !isObservationOnlyWorkSession(metadata)) {
|
|
2813
|
+
return {
|
|
2814
|
+
score: 0.15,
|
|
2815
|
+
signals: [{ signal: "observation-only", weight: 0.15, value: context.commands[0] }],
|
|
2816
|
+
};
|
|
2817
|
+
}
|
|
2812
2818
|
addSignalIfOverlap(signals, "sameTraceId", 0.4, context.traces, metadata.traces);
|
|
2813
2819
|
addSignalIfOverlap(signals, "sameManifestService", 0.35, context.services, metadata.services);
|
|
2814
2820
|
addSignalIfOverlap(signals, "sameRuntimeEntry", 0.3, context.entries, metadata.entries);
|
|
@@ -2951,6 +2957,9 @@ function normalizeWorkSessionMetadata(value: Record<string, unknown>): DeltaWork
|
|
|
2951
2957
|
}
|
|
2952
2958
|
|
|
2953
2959
|
function inferWorkSessionTitle(context: DeltaOperationContext, metadata: DeltaWorkSessionMetadata): string {
|
|
2960
|
+
if (isObservationOnlyWorkSession(metadata)) {
|
|
2961
|
+
return "Observe project context";
|
|
2962
|
+
}
|
|
2954
2963
|
if (context.kind === "manifest.imported" && metadata.services[0]) {
|
|
2955
2964
|
return `Import ${metadata.services[0]} external service`;
|
|
2956
2965
|
}
|
|
@@ -2979,6 +2988,9 @@ function inferWorkSessionTitle(context: DeltaOperationContext, metadata: DeltaWo
|
|
|
2979
2988
|
}
|
|
2980
2989
|
|
|
2981
2990
|
function inferIntent(context: DeltaOperationContext, metadata: DeltaWorkSessionMetadata): string {
|
|
2991
|
+
if (isObservationOnlyWorkSession(metadata)) {
|
|
2992
|
+
return "context-gathering";
|
|
2993
|
+
}
|
|
2982
2994
|
if (context.kind === "manifest.imported" || metadata.fileClusters.includes("manifest.change")) {
|
|
2983
2995
|
return "external-runtime-import";
|
|
2984
2996
|
}
|
|
@@ -2999,6 +3011,9 @@ function inferIntent(context: DeltaOperationContext, metadata: DeltaWorkSessionM
|
|
|
2999
3011
|
|
|
3000
3012
|
function summarizeWorkSession(metadata: DeltaWorkSessionMetadata): string {
|
|
3001
3013
|
const parts: string[] = [];
|
|
3014
|
+
if (isObservationOnlyWorkSession(metadata)) {
|
|
3015
|
+
return `Observed project context with ${metadata.commands.slice(0, 3).join(", ")}.`;
|
|
3016
|
+
}
|
|
3002
3017
|
if (metadata.services[0]) {
|
|
3003
3018
|
parts.push(`worked on ${metadata.services[0]}`);
|
|
3004
3019
|
}
|
|
@@ -3018,6 +3033,9 @@ function summarizeWorkSession(metadata: DeltaWorkSessionMetadata): string {
|
|
|
3018
3033
|
}
|
|
3019
3034
|
|
|
3020
3035
|
function initialWorkSessionConfidence(context: DeltaOperationContext): number {
|
|
3036
|
+
if (isObservationOnlyContext(context)) {
|
|
3037
|
+
return 0.36;
|
|
3038
|
+
}
|
|
3021
3039
|
if (context.kind === "manifest.imported") {
|
|
3022
3040
|
return 0.78;
|
|
3023
3041
|
}
|
|
@@ -3043,6 +3061,65 @@ function isDiagnosticRepairChain(context: DeltaOperationContext, metadata: Delta
|
|
|
3043
3061
|
);
|
|
3044
3062
|
}
|
|
3045
3063
|
|
|
3064
|
+
const OBSERVATION_COMMAND_PREFIXES = [
|
|
3065
|
+
"agent context",
|
|
3066
|
+
"agent print-context",
|
|
3067
|
+
"agent timeline",
|
|
3068
|
+
"cair query",
|
|
3069
|
+
"cair snapshot",
|
|
3070
|
+
"changed",
|
|
3071
|
+
"delta status",
|
|
3072
|
+
"doctor",
|
|
3073
|
+
"explain",
|
|
3074
|
+
"handoff",
|
|
3075
|
+
"inspect",
|
|
3076
|
+
"live status",
|
|
3077
|
+
"status",
|
|
3078
|
+
"timeline",
|
|
3079
|
+
];
|
|
3080
|
+
|
|
3081
|
+
function isObservationOnlyContext(context: DeltaOperationContext): boolean {
|
|
3082
|
+
return (
|
|
3083
|
+
context.commands.length > 0 &&
|
|
3084
|
+
context.files.length === 0 &&
|
|
3085
|
+
context.entries.length === 0 &&
|
|
3086
|
+
context.diagnostics.length === 0 &&
|
|
3087
|
+
context.proofs.length === 0 &&
|
|
3088
|
+
context.services.length === 0 &&
|
|
3089
|
+
context.traces.length === 0 &&
|
|
3090
|
+
context.commands.every(isObservationCommand)
|
|
3091
|
+
);
|
|
3092
|
+
}
|
|
3093
|
+
|
|
3094
|
+
function isObservationOnlyWorkSession(metadata: DeltaWorkSessionMetadata): boolean {
|
|
3095
|
+
return (
|
|
3096
|
+
metadata.commands.length > 0 &&
|
|
3097
|
+
metadata.files.length === 0 &&
|
|
3098
|
+
metadata.entries.length === 0 &&
|
|
3099
|
+
metadata.diagnostics.length === 0 &&
|
|
3100
|
+
metadata.proofs.length === 0 &&
|
|
3101
|
+
metadata.services.length === 0 &&
|
|
3102
|
+
metadata.traces.length === 0 &&
|
|
3103
|
+
metadata.commands.every(isObservationCommand)
|
|
3104
|
+
);
|
|
3105
|
+
}
|
|
3106
|
+
|
|
3107
|
+
function isObservationCommand(command: string): boolean {
|
|
3108
|
+
const normalized = normalizeForgeCommand(command);
|
|
3109
|
+
return OBSERVATION_COMMAND_PREFIXES.some((prefix) => normalized === prefix || normalized.startsWith(`${prefix} `));
|
|
3110
|
+
}
|
|
3111
|
+
|
|
3112
|
+
function normalizeForgeCommand(command: string): string {
|
|
3113
|
+
return command
|
|
3114
|
+
.trim()
|
|
3115
|
+
.replace(/^node\s+(?:\.\/)?bin\/forge\.mjs(?:\s+|$)/u, "")
|
|
3116
|
+
.replace(/^bun\s+run\s+forge(?:\s+|$)/u, "")
|
|
3117
|
+
.replace(/^npm\s+run\s+forge\s+--(?:\s+|$)/u, "")
|
|
3118
|
+
.replace(/^forge(?:\s+|$)/u, "")
|
|
3119
|
+
.replace(/\s+/gu, " ")
|
|
3120
|
+
.trim();
|
|
3121
|
+
}
|
|
3122
|
+
|
|
3046
3123
|
function hasAnyOverlap(...groups: string[][]): boolean {
|
|
3047
3124
|
for (let index = 0; index < groups.length; index += 2) {
|
|
3048
3125
|
if (intersects(groups[index] ?? [], groups[index + 1] ?? [])) {
|
package/src/forge/version.ts
CHANGED