@jitera/connect 0.1.1
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/.claude-plugin/marketplace.json +14 -0
- package/.claude-plugin/plugin.json +33 -0
- package/.codex-plugin/plugin.json +6 -0
- package/LICENSE +202 -0
- package/README.md +27 -0
- package/bin/jitera-connect.mjs +120 -0
- package/content/checkpoint.md +7 -0
- package/content/instructions.md +21 -0
- package/content/session-start.md +10 -0
- package/hooks/hooks.json +28 -0
- package/hooks/scripts/session-start.mjs +36 -0
- package/hooks/scripts/stop.mjs +39 -0
- package/package.json +42 -0
- package/skills/jitera-context/SKILL.md +64 -0
- package/skills/jitera-memory/SKILL.md +118 -0
- package/skills/jitera-setup/SKILL.md +66 -0
- package/skills/jitera-specs/SKILL.md +108 -0
- package/src/adapters/cursor.mjs +56 -0
- package/src/environments.mjs +46 -0
- package/src/mcp-config.mjs +45 -0
- package/templates/AGENTS.md.tmpl +22 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "jitera",
|
|
3
|
+
"description": "Connect AI coding assistants to Jitera projects: project documentation, source, and shared project memory.",
|
|
4
|
+
"owner": {
|
|
5
|
+
"name": "Jitera"
|
|
6
|
+
},
|
|
7
|
+
"plugins": [
|
|
8
|
+
{
|
|
9
|
+
"name": "jitera-connect",
|
|
10
|
+
"source": "./",
|
|
11
|
+
"description": "Connect this workspace to a Jitera project: read documentation and source, and keep project memory up to date."
|
|
12
|
+
}
|
|
13
|
+
]
|
|
14
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "jitera-connect",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Connect this workspace to a Jitera project: read documentation and source, and keep project memory up to date.",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "Jitera"
|
|
7
|
+
},
|
|
8
|
+
"mcpServers": {
|
|
9
|
+
"jitera": {
|
|
10
|
+
"type": "http",
|
|
11
|
+
"url": "${user_config.jitera_mcp_url}",
|
|
12
|
+
"headers": {
|
|
13
|
+
"Authorization": "Bearer ${user_config.jitera_api_key}"
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"userConfig": {
|
|
18
|
+
"jitera_api_key": {
|
|
19
|
+
"type": "string",
|
|
20
|
+
"title": "API key",
|
|
21
|
+
"description": "Create one in your project settings. Choose read + write access if you want the assistant to save memory and write specs.",
|
|
22
|
+
"sensitive": true,
|
|
23
|
+
"required": true
|
|
24
|
+
},
|
|
25
|
+
"jitera_mcp_url": {
|
|
26
|
+
"type": "string",
|
|
27
|
+
"title": "MCP endpoint",
|
|
28
|
+
"description": "Leave as-is for Jitera Cloud. Self-hosted and non-production deployments use a different host.",
|
|
29
|
+
"default": "https://gateway-proxy.jitera.app/gateway/boost/mcp",
|
|
30
|
+
"required": true
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
package/LICENSE
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
|
|
2
|
+
Apache License
|
|
3
|
+
Version 2.0, January 2004
|
|
4
|
+
http://www.apache.org/licenses/
|
|
5
|
+
|
|
6
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
7
|
+
|
|
8
|
+
1. Definitions.
|
|
9
|
+
|
|
10
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
11
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
12
|
+
|
|
13
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
14
|
+
the copyright owner that is granting the License.
|
|
15
|
+
|
|
16
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
17
|
+
other entities that control, are controlled by, or are under common
|
|
18
|
+
control with that entity. For the purposes of this definition,
|
|
19
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
20
|
+
direction or management of such entity, whether by contract or
|
|
21
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
22
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
23
|
+
|
|
24
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
25
|
+
exercising permissions granted by this License.
|
|
26
|
+
|
|
27
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
28
|
+
including but not limited to software source code, documentation
|
|
29
|
+
source, and configuration files.
|
|
30
|
+
|
|
31
|
+
"Object" form shall mean any form resulting from mechanical
|
|
32
|
+
transformation or translation of a Source form, including but
|
|
33
|
+
not limited to compiled object code, generated documentation,
|
|
34
|
+
and conversions to other media types.
|
|
35
|
+
|
|
36
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
37
|
+
Object form, made available under the License, as indicated by a
|
|
38
|
+
copyright notice that is included in or attached to the work
|
|
39
|
+
(an example is provided in the Appendix below).
|
|
40
|
+
|
|
41
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
42
|
+
form, that is based on (or derived from) the Work and for which the
|
|
43
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
44
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
45
|
+
of this License, Derivative Works shall not include works that remain
|
|
46
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
47
|
+
the Work and Derivative Works thereof.
|
|
48
|
+
|
|
49
|
+
"Contribution" shall mean any work of authorship, including
|
|
50
|
+
the original version of the Work and any modifications or additions
|
|
51
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
52
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
53
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
54
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
55
|
+
means any form of electronic, verbal, or written communication sent
|
|
56
|
+
to the Licensor or its representatives, including but not limited to
|
|
57
|
+
communication on electronic mailing lists, source code control systems,
|
|
58
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
59
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
60
|
+
excluding communication that is conspicuously marked or otherwise
|
|
61
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
62
|
+
|
|
63
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
64
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
65
|
+
subsequently incorporated within the Work.
|
|
66
|
+
|
|
67
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
68
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
69
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
70
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
71
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
72
|
+
Work and such Derivative Works in Source or Object form.
|
|
73
|
+
|
|
74
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
75
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
76
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
77
|
+
(except as stated in this section) patent license to make, have made,
|
|
78
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
79
|
+
where such license applies only to those patent claims licensable
|
|
80
|
+
by such Contributor that are necessarily infringed by their
|
|
81
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
82
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
83
|
+
institute patent litigation against any entity (including a
|
|
84
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
85
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
86
|
+
or contributory patent infringement, then any patent licenses
|
|
87
|
+
granted to You under this License for that Work shall terminate
|
|
88
|
+
as of the date such litigation is filed.
|
|
89
|
+
|
|
90
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
91
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
92
|
+
modifications, and in Source or Object form, provided that You
|
|
93
|
+
meet the following conditions:
|
|
94
|
+
|
|
95
|
+
(a) You must give any other recipients of the Work or
|
|
96
|
+
Derivative Works a copy of this License; and
|
|
97
|
+
|
|
98
|
+
(b) You must cause any modified files to carry prominent notices
|
|
99
|
+
stating that You changed the files; and
|
|
100
|
+
|
|
101
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
102
|
+
that You distribute, all copyright, patent, trademark, and
|
|
103
|
+
attribution notices from the Source form of the Work,
|
|
104
|
+
excluding those notices that do not pertain to any part of
|
|
105
|
+
the Derivative Works; and
|
|
106
|
+
|
|
107
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
108
|
+
distribution, then any Derivative Works that You distribute must
|
|
109
|
+
include a readable copy of the attribution notices contained
|
|
110
|
+
within such NOTICE file, excluding those notices that do not
|
|
111
|
+
pertain to any part of the Derivative Works, in at least one
|
|
112
|
+
of the following places: within a NOTICE text file distributed
|
|
113
|
+
as part of the Derivative Works; within the Source form or
|
|
114
|
+
documentation, if provided along with the Derivative Works; or,
|
|
115
|
+
within a display generated by the Derivative Works, if and
|
|
116
|
+
wherever such third-party notices normally appear. The contents
|
|
117
|
+
of the NOTICE file are for informational purposes only and
|
|
118
|
+
do not modify the License. You may add Your own attribution
|
|
119
|
+
notices within Derivative Works that You distribute, alongside
|
|
120
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
121
|
+
that such additional attribution notices cannot be construed
|
|
122
|
+
as modifying the License.
|
|
123
|
+
|
|
124
|
+
You may add Your own copyright statement to Your modifications and
|
|
125
|
+
may provide additional or different license terms and conditions
|
|
126
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
127
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
128
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
129
|
+
the conditions stated in this License.
|
|
130
|
+
|
|
131
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
132
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
133
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
134
|
+
this License, without any additional terms or conditions.
|
|
135
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
136
|
+
the terms of any separate license agreement you may have executed
|
|
137
|
+
with Licensor regarding such Contributions.
|
|
138
|
+
|
|
139
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
140
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
141
|
+
except as required for reasonable and customary use in describing the
|
|
142
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
143
|
+
|
|
144
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
145
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
146
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
147
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
148
|
+
implied, including, without limitation, any warranties or conditions
|
|
149
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
150
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
151
|
+
appropriateness of using or redistributing the Work and assume any
|
|
152
|
+
risks associated with Your exercise of permissions under this License.
|
|
153
|
+
|
|
154
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
155
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
156
|
+
unless required by applicable law (such as deliberate and grossly
|
|
157
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
158
|
+
liable to You for damages, including any direct, indirect, special,
|
|
159
|
+
incidental, or consequential damages of any character arising as a
|
|
160
|
+
result of this License or out of the use or inability to use the
|
|
161
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
162
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
163
|
+
other commercial damages or losses), even if such Contributor
|
|
164
|
+
has been advised of the possibility of such damages.
|
|
165
|
+
|
|
166
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
167
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
168
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
169
|
+
or other liability obligations and/or rights consistent with this
|
|
170
|
+
License. However, in accepting such obligations, You may act only
|
|
171
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
172
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
173
|
+
defend, and hold each Contributor harmless for any liability
|
|
174
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
175
|
+
of your accepting any such warranty or additional liability.
|
|
176
|
+
|
|
177
|
+
END OF TERMS AND CONDITIONS
|
|
178
|
+
|
|
179
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
180
|
+
|
|
181
|
+
To apply the Apache License to your work, attach the following
|
|
182
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
183
|
+
replaced with your own identifying information. (Don't include
|
|
184
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
185
|
+
comment syntax for the file format. We also recommend that a
|
|
186
|
+
file or class name and description of purpose be included on the
|
|
187
|
+
same "printed page" as the copyright notice for easier
|
|
188
|
+
identification within third-party archives.
|
|
189
|
+
|
|
190
|
+
Copyright 2026 Jitera
|
|
191
|
+
|
|
192
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
193
|
+
you may not use this file except in compliance with the License.
|
|
194
|
+
You may obtain a copy of the License at
|
|
195
|
+
|
|
196
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
197
|
+
|
|
198
|
+
Unless required by applicable law or agreed to in writing, software
|
|
199
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
200
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
201
|
+
See the License for the specific language governing permissions and
|
|
202
|
+
limitations under the License.
|
package/README.md
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# how to install
|
|
2
|
+
|
|
3
|
+
## claude code
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
/plugin marketplace add jitera-product/connect
|
|
7
|
+
/plugin install jitera-connect
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
## codex
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
codex plugin marketplace add jitera-product/connect
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## cursor
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
npx @jitera-product/connect
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## pilot and staging environments
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
npx @jitera-product/connect --env=studio-stage
|
|
26
|
+
npx @jitera-product/connect --env=studio-06
|
|
27
|
+
```
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
|
|
4
|
+
import { cursor } from "../src/adapters/cursor.mjs";
|
|
5
|
+
import { MalformedConfigError } from "../src/mcp-config.mjs";
|
|
6
|
+
import { UnknownEnvironmentError, resolveApiBaseUrl, resolveMcpUrl } from "../src/environments.mjs";
|
|
7
|
+
|
|
8
|
+
const ADAPTERS = [cursor];
|
|
9
|
+
|
|
10
|
+
function parseArgs(argv) {
|
|
11
|
+
const args = { scope: "project" };
|
|
12
|
+
for (const arg of argv) {
|
|
13
|
+
if (arg.startsWith("--env=")) args.env = arg.slice("--env=".length);
|
|
14
|
+
else if (arg === "--dry-run") args.dryRun = true;
|
|
15
|
+
else if (arg === "--uninstall") args.uninstall = true;
|
|
16
|
+
else if (arg === "--user") args.scope = "user";
|
|
17
|
+
else if (arg === "--project") args.scope = "project";
|
|
18
|
+
else if (arg === "--print") args.print = true;
|
|
19
|
+
else if (arg === "--help" || arg === "-h") args.help = true;
|
|
20
|
+
else args.unknown = arg;
|
|
21
|
+
}
|
|
22
|
+
return args;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function usage() {
|
|
26
|
+
return [
|
|
27
|
+
"usage: npx @jitera/connect [--env=<environment>] [options]",
|
|
28
|
+
"",
|
|
29
|
+
" --env=studio-stage staging",
|
|
30
|
+
" --env=studio-06 numbered pilot",
|
|
31
|
+
" omit --env for production",
|
|
32
|
+
"",
|
|
33
|
+
" --user write user scoped config instead of project scoped",
|
|
34
|
+
" --dry-run report what would change without writing",
|
|
35
|
+
" --uninstall remove the jitera server",
|
|
36
|
+
" --print print resolved endpoints and exit",
|
|
37
|
+
].join("\n");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function main() {
|
|
41
|
+
const args = parseArgs(process.argv.slice(2));
|
|
42
|
+
const out = process.stdout;
|
|
43
|
+
|
|
44
|
+
if (args.help) {
|
|
45
|
+
out.write(`${usage()}\n`);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (args.unknown) {
|
|
50
|
+
process.stderr.write(`error: unrecognised argument "${args.unknown}"\n${usage()}\n`);
|
|
51
|
+
process.exitCode = 2;
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
let mcpUrl;
|
|
56
|
+
let apiBaseUrl;
|
|
57
|
+
try {
|
|
58
|
+
mcpUrl = resolveMcpUrl(args.env);
|
|
59
|
+
apiBaseUrl = resolveApiBaseUrl(args.env);
|
|
60
|
+
} catch (error) {
|
|
61
|
+
if (error instanceof UnknownEnvironmentError) {
|
|
62
|
+
process.stderr.write(`error: ${error.message}\n`);
|
|
63
|
+
process.exitCode = 2;
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
throw error;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (args.print) {
|
|
70
|
+
out.write(`${JSON.stringify({ mcpUrl, apiBaseUrl }, undefined, 2)}\n`);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const context = { scope: args.scope, home: homedir(), cwd: process.cwd(), mcpUrl, dryRun: args.dryRun };
|
|
75
|
+
const detected = ADAPTERS.filter((adapter) => adapter.detect(context));
|
|
76
|
+
|
|
77
|
+
if (detected.length === 0) {
|
|
78
|
+
process.stderr.write(
|
|
79
|
+
`error: no supported assistant detected. Looked for: ${ADAPTERS.map((a) => a.label).join(", ")}.\n` +
|
|
80
|
+
`Claude Code and Codex install through their own plugin marketplaces, see the readme.\n`
|
|
81
|
+
);
|
|
82
|
+
process.exitCode = 1;
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const results = [];
|
|
87
|
+
for (const adapter of detected) {
|
|
88
|
+
try {
|
|
89
|
+
results.push({
|
|
90
|
+
adapter,
|
|
91
|
+
result: args.uninstall ? adapter.uninstall(context) : adapter.install(context),
|
|
92
|
+
});
|
|
93
|
+
} catch (error) {
|
|
94
|
+
if (error instanceof MalformedConfigError) {
|
|
95
|
+
process.stderr.write(`error: ${error.message}\n`);
|
|
96
|
+
process.exitCode = 1;
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
throw error;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const verb = args.uninstall ? "removed from" : "written to";
|
|
104
|
+
for (const { adapter, result } of results) {
|
|
105
|
+
const state = result.changed ? verb : "already up to date in";
|
|
106
|
+
out.write(`${adapter.label}: ${state} ${result.path}\n`);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (args.dryRun) {
|
|
110
|
+
out.write("\ndry run, nothing was written\n");
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (!args.uninstall) {
|
|
115
|
+
out.write(`\nendpoint ${mcpUrl}\n`);
|
|
116
|
+
out.write("export JITERA_API_KEY=<your api key> before starting your assistant\n");
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
main();
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Checkpoint. If decisions, constraints, conventions, or corrections have come up
|
|
2
|
+
in this session that are not yet in project memory, persist them now with
|
|
3
|
+
`remember_jitera_memory`.
|
|
4
|
+
|
|
5
|
+
Recall each entity first and re-send its full attribute list, so you update it
|
|
6
|
+
rather than creating a duplicate. Skip anything already recorded in the
|
|
7
|
+
repository or its git history.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{{BRAND}} project context is available through this server: the project's
|
|
2
|
+
memory, documentation, and source. Treat it as the source of truth for this
|
|
3
|
+
project, not an optional lookup.
|
|
4
|
+
|
|
5
|
+
Before answering a question about this project, and before planning or
|
|
6
|
+
implementing anything, call `recall_jitera_memory` first. An empty result is
|
|
7
|
+
normal and is not an error.
|
|
8
|
+
|
|
9
|
+
Before writing a spec, design, or plan, search `documents/` with
|
|
10
|
+
`resource_search` and read any match with `resource_read`. Update the existing
|
|
11
|
+
document rather than creating a second one.
|
|
12
|
+
|
|
13
|
+
When a decision is made, a constraint is discovered, or the user corrects you,
|
|
14
|
+
persist it with `remember_jitera_memory`. Recall the entity first and re-send its
|
|
15
|
+
full attribute list — entities are keyed on name plus type, so writing blind
|
|
16
|
+
creates duplicates and replaces facts instead of adding them.
|
|
17
|
+
|
|
18
|
+
Do not store anything already recorded in the repository or its git history.
|
|
19
|
+
|
|
20
|
+
Domains: `documents/` is readable and writable; `sources/` and `uploads/` are
|
|
21
|
+
read-only. Write tools require an API key created with read + write access.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
Project context tools are connected for this workspace: project documentation,
|
|
2
|
+
source, and a shared project memory.
|
|
3
|
+
|
|
4
|
+
Before your first substantive answer in this session, and before planning or
|
|
5
|
+
implementing anything, call `recall_jitera_memory`. An empty result is normal and
|
|
6
|
+
is not an error.
|
|
7
|
+
|
|
8
|
+
As decisions are made, constraints are discovered, or you are corrected, persist
|
|
9
|
+
them with `remember_jitera_memory`. Recall the entity first and re-send its full
|
|
10
|
+
attribute list, so you update it rather than creating a duplicate.
|
package/hooks/hooks.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"hooks": {
|
|
3
|
+
"SessionStart": [
|
|
4
|
+
{
|
|
5
|
+
"hooks": [
|
|
6
|
+
{
|
|
7
|
+
"type": "command",
|
|
8
|
+
"command": "node",
|
|
9
|
+
"args": ["${CLAUDE_PLUGIN_ROOT}/hooks/scripts/session-start.mjs"],
|
|
10
|
+
"timeout": 10
|
|
11
|
+
}
|
|
12
|
+
]
|
|
13
|
+
}
|
|
14
|
+
],
|
|
15
|
+
"Stop": [
|
|
16
|
+
{
|
|
17
|
+
"hooks": [
|
|
18
|
+
{
|
|
19
|
+
"type": "command",
|
|
20
|
+
"command": "node",
|
|
21
|
+
"args": ["${CLAUDE_PLUGIN_ROOT}/hooks/scripts/stop.mjs"],
|
|
22
|
+
"timeout": 10
|
|
23
|
+
}
|
|
24
|
+
]
|
|
25
|
+
}
|
|
26
|
+
]
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { join, dirname } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
|
|
6
|
+
const ROOT = join(dirname(fileURLToPath(import.meta.url)), "..", "..");
|
|
7
|
+
|
|
8
|
+
function drainStdin() {
|
|
9
|
+
try {
|
|
10
|
+
readFileSync(0, "utf8");
|
|
11
|
+
} catch {
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function main() {
|
|
17
|
+
drainStdin();
|
|
18
|
+
|
|
19
|
+
let directive = "";
|
|
20
|
+
try {
|
|
21
|
+
directive = readFileSync(join(ROOT, "content", "session-start.md"), "utf8").trim();
|
|
22
|
+
} catch {
|
|
23
|
+
process.exit(0);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
process.stdout.write(
|
|
27
|
+
JSON.stringify({
|
|
28
|
+
hookSpecificOutput: {
|
|
29
|
+
hookEventName: "SessionStart",
|
|
30
|
+
additionalContext: directive,
|
|
31
|
+
},
|
|
32
|
+
})
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
main();
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { join, dirname } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
|
|
6
|
+
const ROOT = join(dirname(fileURLToPath(import.meta.url)), "..", "..");
|
|
7
|
+
const CHECKPOINT_EVERY_TURNS = 5;
|
|
8
|
+
|
|
9
|
+
function readInput() {
|
|
10
|
+
try {
|
|
11
|
+
return JSON.parse(readFileSync(0, "utf8"));
|
|
12
|
+
} catch {
|
|
13
|
+
return {};
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function main() {
|
|
18
|
+
const turn = readInput().turn_number;
|
|
19
|
+
if (!Number.isInteger(turn) || turn < 1) process.exit(0);
|
|
20
|
+
if (turn % CHECKPOINT_EVERY_TURNS !== 0) process.exit(0);
|
|
21
|
+
|
|
22
|
+
let directive = "";
|
|
23
|
+
try {
|
|
24
|
+
directive = readFileSync(join(ROOT, "content", "checkpoint.md"), "utf8").trim();
|
|
25
|
+
} catch {
|
|
26
|
+
process.exit(0);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
process.stdout.write(
|
|
30
|
+
JSON.stringify({
|
|
31
|
+
hookSpecificOutput: {
|
|
32
|
+
hookEventName: "Stop",
|
|
33
|
+
additionalContext: directive,
|
|
34
|
+
},
|
|
35
|
+
})
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
main();
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@jitera/connect",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Connect AI coding assistants to Jitera projects: documentation, source, and shared project memory",
|
|
6
|
+
"license": "Apache-2.0",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/Jitera-Product/connect.git"
|
|
10
|
+
},
|
|
11
|
+
"keywords": [
|
|
12
|
+
"jitera",
|
|
13
|
+
"mcp",
|
|
14
|
+
"agent-skills",
|
|
15
|
+
"claude-code",
|
|
16
|
+
"codex",
|
|
17
|
+
"cursor"
|
|
18
|
+
],
|
|
19
|
+
"engines": {
|
|
20
|
+
"node": ">=20"
|
|
21
|
+
},
|
|
22
|
+
"bin": {
|
|
23
|
+
"jitera-connect": "./bin/jitera-connect.mjs"
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"bin/",
|
|
27
|
+
"src/",
|
|
28
|
+
"skills/",
|
|
29
|
+
"hooks/",
|
|
30
|
+
"content/",
|
|
31
|
+
"templates/",
|
|
32
|
+
".claude-plugin/",
|
|
33
|
+
".codex-plugin/"
|
|
34
|
+
],
|
|
35
|
+
"publishConfig": {
|
|
36
|
+
"access": "public"
|
|
37
|
+
},
|
|
38
|
+
"scripts": {
|
|
39
|
+
"test": "node --test \"test/**/*.test.mjs\"",
|
|
40
|
+
"validate": "node scripts/validate.mjs"
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: jitera-context
|
|
3
|
+
description: Search and read {{BRAND}} project resources — documentation, source code, and uploaded files. Use when you need project context you do not already have, when the user refers to a file, document, or upload by name, and before answering questions about how this project works.
|
|
4
|
+
license: Apache-2.0
|
|
5
|
+
metadata:
|
|
6
|
+
author: jitera
|
|
7
|
+
version: "1.0"
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Reading {{BRAND}} project resources
|
|
11
|
+
|
|
12
|
+
Three virtual domains:
|
|
13
|
+
|
|
14
|
+
| Domain | Contents | Writable |
|
|
15
|
+
| --- | --- | --- |
|
|
16
|
+
| `documents/` | Project documentation — markdown, HTML, PDFs | yes |
|
|
17
|
+
| `sources/` | Project source code | no |
|
|
18
|
+
| `uploads/` | Uploaded files | no |
|
|
19
|
+
|
|
20
|
+
## Searching
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
resource_search(pattern="documents/**/*.md", content="refund", limit=30, offset=0)
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
- `pattern` — a glob over virtual paths supporting `*`, `**`, and `?`. Omit the
|
|
27
|
+
domain prefix to search every domain: `resource_search(pattern="**/checkout*.*")`
|
|
28
|
+
- `content` — a grep-style match inside files, supporting boolean expressions:
|
|
29
|
+
`content="(api OR rest) AND controller"`
|
|
30
|
+
- Results are grouped by domain. Open a match with `resource_read`
|
|
31
|
+
|
|
32
|
+
Search before reading. Do not guess paths.
|
|
33
|
+
|
|
34
|
+
## Reading
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
resource_read(path="sources/frontend/src/App.tsx", line_from=1, line_number=300)
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
- A file path returns content with line numbers; a directory path returns a
|
|
41
|
+
listing. `resource_read(path="/")` lists the root
|
|
42
|
+
- `line_from` is 1-indexed; `line_number` defaults to 300
|
|
43
|
+
- For large PDFs read on the fly, `line_from` selects the starting **page**
|
|
44
|
+
rather than the line. The returned window ends with the `line_from` value to
|
|
45
|
+
pass in order to read further
|
|
46
|
+
|
|
47
|
+
## Documents that are mostly images
|
|
48
|
+
|
|
49
|
+
`analyze_document(url, requirements)` extracts information by looking at a
|
|
50
|
+
document rather than reading its text. Use it for scanned PDFs, diagrams,
|
|
51
|
+
screenshots, and spreadsheets where layout carries meaning.
|
|
52
|
+
|
|
53
|
+
## Budget
|
|
54
|
+
|
|
55
|
+
Search narrowly, then read the specific range you need. Reading whole large files
|
|
56
|
+
to locate one function spends the context you need for the actual work.
|
|
57
|
+
|
|
58
|
+
## Failures
|
|
59
|
+
|
|
60
|
+
| Symptom | Cause | What to do |
|
|
61
|
+
| --- | --- | --- |
|
|
62
|
+
| A domain reports that it is disabled | That read domain is turned off for this agent or project | Tell the user which domain is disabled. Do not retry. |
|
|
63
|
+
| Search returns nothing | Pattern too narrow, or the wrong domain | Drop the domain prefix and widen the glob before concluding the file is absent |
|
|
64
|
+
| One path errors while a similar one works | The project has two path conventions and the editor form differs | Re-run `resource_search` and use the exact path it returns |
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: jitera-memory
|
|
3
|
+
description: Recall and maintain {{BRAND}} project memory — stored decisions, constraints, conventions, and domain knowledge shared across the project. Use before planning or implementing anything, when the user refers to earlier work or past decisions, and after a decision is made, a constraint is discovered, or the user corrects you.
|
|
4
|
+
license: Apache-2.0
|
|
5
|
+
metadata:
|
|
6
|
+
author: jitera
|
|
7
|
+
version: "1.0"
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# {{BRAND}} project memory
|
|
11
|
+
|
|
12
|
+
Project memory is a graph of entities. Each entity has a `name`, a `type`, and a
|
|
13
|
+
list of `attrs` — short factual statements. Entities reference each other with
|
|
14
|
+
`[[id]]` links.
|
|
15
|
+
|
|
16
|
+
Memory is shared with everyone working on the project. What you write, your
|
|
17
|
+
teammates read.
|
|
18
|
+
|
|
19
|
+
## Recall before you act
|
|
20
|
+
|
|
21
|
+
Call `recall_jitera_memory` before:
|
|
22
|
+
|
|
23
|
+
- starting any task in this project
|
|
24
|
+
- planning or proposing an approach
|
|
25
|
+
- making an architectural or naming decision
|
|
26
|
+
- answering anything that refers to earlier work — "like we did before", "the
|
|
27
|
+
usual pattern", "what did we decide about…"
|
|
28
|
+
|
|
29
|
+
An empty result is normal. It means nothing is stored yet, not that the call
|
|
30
|
+
failed.
|
|
31
|
+
|
|
32
|
+
### When the first recall comes back thin
|
|
33
|
+
|
|
34
|
+
`recall_jitera_memory` filters entities by keywords extracted from `query`. Work
|
|
35
|
+
down this ladder, stopping as soon as you have what you need:
|
|
36
|
+
|
|
37
|
+
1. `recall_jitera_memory(query="<specific terms from the task>")`
|
|
38
|
+
2. `recall_jitera_memory(query="<the subsystem or domain>")`
|
|
39
|
+
3. `recall_jitera_memory()` with no query — returns every stored entity
|
|
40
|
+
|
|
41
|
+
Do not conclude that memory is empty until step 3 returns nothing. Results
|
|
42
|
+
containing `[[id]]` references mean related entities exist, and a broader query
|
|
43
|
+
will surface them.
|
|
44
|
+
|
|
45
|
+
## Remember when knowledge is created
|
|
46
|
+
|
|
47
|
+
Persist with `remember_jitera_memory(name, type, attrs)` when:
|
|
48
|
+
|
|
49
|
+
- a decision is made — including why it was chosen over the alternative
|
|
50
|
+
- the user corrects you, or rejects an approach
|
|
51
|
+
- you discover a constraint that is not obvious from the code: a rate limit, an
|
|
52
|
+
ordering requirement, a service that must be restarted, a deploy gotcha
|
|
53
|
+
- you establish a convention future work should follow
|
|
54
|
+
- a session ends with something worth carrying forward
|
|
55
|
+
|
|
56
|
+
### Recall before you remember
|
|
57
|
+
|
|
58
|
+
`remember_jitera_memory` upserts on `name` + `type`, and `attrs` **replaces** the
|
|
59
|
+
existing list rather than appending to it. Writing without recalling first either
|
|
60
|
+
fragments the graph into near-duplicates or silently drops facts:
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
Checkout Service (Service) — attrs: [...]
|
|
64
|
+
Checkout service (Service) — attrs: [...]
|
|
65
|
+
CheckoutService (Service) — attrs: [...]
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Procedure:
|
|
69
|
+
|
|
70
|
+
1. `recall_jitera_memory(query="<entity name>")`
|
|
71
|
+
2. If it exists, re-send it with the **full** attribute list — every existing
|
|
72
|
+
fact plus the new one
|
|
73
|
+
3. If it does not exist, create it using the name form already used elsewhere in
|
|
74
|
+
the project
|
|
75
|
+
|
|
76
|
+
### Shape of a good entity
|
|
77
|
+
|
|
78
|
+
| Field | Guidance |
|
|
79
|
+
| --- | --- |
|
|
80
|
+
| `name` | The display name people actually use. Match existing casing. |
|
|
81
|
+
| `type` | A stable category — `Service`, `Decision`, `Constraint`, `Convention`, `Person`, `System`. Reuse types already in the graph. |
|
|
82
|
+
| `attrs` | Short, self-contained statements. One fact per entry. |
|
|
83
|
+
|
|
84
|
+
Good:
|
|
85
|
+
|
|
86
|
+
```
|
|
87
|
+
name: "Checkout Service"
|
|
88
|
+
type: "Service"
|
|
89
|
+
attrs: ["Owns payment capture and refunds",
|
|
90
|
+
"Reaches Stripe through the billing gateway, never directly",
|
|
91
|
+
"Refunds are idempotent on order id, not payment id"]
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Poor — vague, and re-derivable from the code:
|
|
95
|
+
|
|
96
|
+
```
|
|
97
|
+
name: "checkout"
|
|
98
|
+
type: "thing"
|
|
99
|
+
attrs: ["handles checkout stuff", "has files in src/checkout"]
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## What not to store
|
|
103
|
+
|
|
104
|
+
- Anything derivable from the repository or its git history — file layout,
|
|
105
|
+
function signatures, what a past commit changed
|
|
106
|
+
- Transient state — what you are doing right now, a branch name, a test you are
|
|
107
|
+
part-way through fixing
|
|
108
|
+
- Secrets, credentials, tokens, or customer personal data
|
|
109
|
+
- Long prose. If it needs paragraphs, write a document (see the `jitera-specs`
|
|
110
|
+
skill) and store a one-line pointer to it in memory
|
|
111
|
+
|
|
112
|
+
## Failures
|
|
113
|
+
|
|
114
|
+
| Symptom | Cause | What to do |
|
|
115
|
+
| --- | --- | --- |
|
|
116
|
+
| `remember_jitera_memory` rejected as read-only | The API key was created with read access only | Tell the user the key needs read + write access, created in project settings. Do not retry. |
|
|
117
|
+
| Recall returns unrelated entities | Keyword extraction matched loosely | Narrow the query, or recall with no query and filter the results yourself |
|
|
118
|
+
| Recall is still empty at step 3 | Memory genuinely is empty | Proceed, and record what you learn as you go |
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: jitera-setup
|
|
3
|
+
description: Diagnose a broken {{BRAND}} connection — missing tools, authentication failures, read-only API keys, or the wrong project. Use when {{BRAND}} tools are absent from the tool list, when a call fails with an authentication or authorization error, or when the data returned belongs to a different project than expected.
|
|
4
|
+
license: Apache-2.0
|
|
5
|
+
metadata:
|
|
6
|
+
author: jitera
|
|
7
|
+
version: "1.0"
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Diagnosing a {{BRAND}} connection
|
|
11
|
+
|
|
12
|
+
Work through these in order. Report the specific cause you find — never tell the
|
|
13
|
+
user only that something failed.
|
|
14
|
+
|
|
15
|
+
## 1. Are the tools present?
|
|
16
|
+
|
|
17
|
+
If `resource_search`, `resource_read`, and `recall_jitera_memory` are missing from
|
|
18
|
+
your tool list, the MCP server is not connected at all. This is not a permissions
|
|
19
|
+
problem and no API key will fix it.
|
|
20
|
+
|
|
21
|
+
Ask the user to confirm the server is configured, then to restart the assistant.
|
|
22
|
+
Most clients load MCP servers only at startup.
|
|
23
|
+
|
|
24
|
+
## 2. Does any call succeed?
|
|
25
|
+
|
|
26
|
+
Try the cheapest possible read:
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
resource_read(path="/")
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
| Result | Meaning |
|
|
33
|
+
| --- | --- |
|
|
34
|
+
| A directory listing | Connection and key are fine; the problem is elsewhere |
|
|
35
|
+
| Unauthorized or authentication error | The key is missing, wrong, revoked, or expired |
|
|
36
|
+
| An empty listing | Connected, but the project has no resources yet |
|
|
37
|
+
|
|
38
|
+
## 3. Is the key read-only?
|
|
39
|
+
|
|
40
|
+
A read that works alongside a write that fails with a read-only message means the
|
|
41
|
+
key was created with read access only.
|
|
42
|
+
|
|
43
|
+
Writes need a key created with **read + write** access. The access level is fixed
|
|
44
|
+
when the key is created and cannot be changed afterwards, so this needs a new key
|
|
45
|
+
from project settings.
|
|
46
|
+
|
|
47
|
+
## 4. Is it the right project?
|
|
48
|
+
|
|
49
|
+
An API key is bound to one project. If the resources returned belong to a
|
|
50
|
+
different project than the user expects, the key belongs to that other project.
|
|
51
|
+
|
|
52
|
+
Confirm with `resource_read(path="/")` and describe what you see back to the user
|
|
53
|
+
so they can recognise it.
|
|
54
|
+
|
|
55
|
+
## 5. Is memory scoped more narrowly than expected?
|
|
56
|
+
|
|
57
|
+
A key bound to a specific agent sees only that agent's memory partition. A
|
|
58
|
+
project-level key sees project-wide memory. If recall returns less than the user
|
|
59
|
+
expects, the key is probably agent-scoped.
|
|
60
|
+
|
|
61
|
+
## What not to do
|
|
62
|
+
|
|
63
|
+
- Do not retry an authentication failure or a read-only rejection — neither is
|
|
64
|
+
transient, and retrying only delays the real fix
|
|
65
|
+
- Do not invent a workaround, such as writing a spec into the local repository
|
|
66
|
+
when the intent was to store it in the project. State what is blocked and why
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: jitera-specs
|
|
3
|
+
description: Write, find, and maintain specifications and design documents in {{BRAND}} project documentation. Use before implementing a feature, to find the spec that already describes it; when asked to write a spec, design, plan, or decision record; and when an implementation has diverged from the document describing it.
|
|
4
|
+
license: Apache-2.0
|
|
5
|
+
metadata:
|
|
6
|
+
author: jitera
|
|
7
|
+
version: "1.0"
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Specs in {{BRAND}} documentation
|
|
11
|
+
|
|
12
|
+
Specs live in the `documents/` domain and are shared with the whole project. They
|
|
13
|
+
are living documents — written once, then read and revised as the work proceeds.
|
|
14
|
+
|
|
15
|
+
`documents/` is the only writable domain. `sources/` and `uploads/` are read-only.
|
|
16
|
+
|
|
17
|
+
## Before implementing anything
|
|
18
|
+
|
|
19
|
+
1. `resource_search(pattern="documents/**/*.md", content="<feature terms>")`
|
|
20
|
+
2. `resource_read(path="documents/<match>")` on anything plausible
|
|
21
|
+
3. Write a new spec only when nothing covers it
|
|
22
|
+
|
|
23
|
+
Writing a second spec for something already specified is the most common failure
|
|
24
|
+
here. Two documents describing the same feature diverge immediately, and
|
|
25
|
+
afterwards neither can be trusted.
|
|
26
|
+
|
|
27
|
+
## Writing a spec
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
resource_write(
|
|
31
|
+
path="documents/specs/checkout-refunds.md",
|
|
32
|
+
operation="create",
|
|
33
|
+
content="<full markdown>"
|
|
34
|
+
)
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Path conventions:
|
|
38
|
+
|
|
39
|
+
- `documents/specs/<feature>.md` — specifications and designs
|
|
40
|
+
- `documents/decisions/<topic>.md` — decision records
|
|
41
|
+
|
|
42
|
+
Follow whatever convention the project already uses. Check with
|
|
43
|
+
`resource_search(pattern="documents/**")` before inventing one.
|
|
44
|
+
|
|
45
|
+
## Updating a spec
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
resource_write(
|
|
49
|
+
path="documents/specs/checkout-refunds.md",
|
|
50
|
+
operation="update",
|
|
51
|
+
content="<full markdown>"
|
|
52
|
+
)
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
- `update` resolves the document by `path`, or by `uuid` when you pass one
|
|
56
|
+
- Updates apply as a diff by default. Pass `force_replace=true` only when you
|
|
57
|
+
intend to replace the whole document
|
|
58
|
+
- Prefer updating over creating a second document, even for large changes
|
|
59
|
+
|
|
60
|
+
Update the spec when the implementation diverges from it, when a decision it
|
|
61
|
+
records is reversed, or when you find it was wrong. A spec that no longer matches
|
|
62
|
+
the code is worse than no spec, because it is trusted and wrong.
|
|
63
|
+
|
|
64
|
+
## Moving and deleting
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
resource_write(path="documents/old.md", operation="move",
|
|
68
|
+
new_path="documents/specs/new.md")
|
|
69
|
+
resource_write(path="documents/obsolete.md", operation="delete")
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Generated drafts
|
|
73
|
+
|
|
74
|
+
Passing `requirements` instead of `content` has the document generated from a
|
|
75
|
+
description rather than written by you:
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
resource_write(
|
|
79
|
+
path="documents/specs/checkout-refunds.md",
|
|
80
|
+
requirements="Specification for idempotent refunds, keyed on order id",
|
|
81
|
+
format="md"
|
|
82
|
+
)
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Write `content` yourself by default. Use `requirements` only when the user asks
|
|
86
|
+
for a generated draft.
|
|
87
|
+
|
|
88
|
+
## After writing
|
|
89
|
+
|
|
90
|
+
Record that the spec exists, so a future session finds it without searching. See
|
|
91
|
+
the `jitera-memory` skill for the recall-before-remember rule:
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
remember_jitera_memory(
|
|
95
|
+
name="Refunds specification",
|
|
96
|
+
type="Decision",
|
|
97
|
+
attrs=["Specified in documents/specs/checkout-refunds.md",
|
|
98
|
+
"Refunds are idempotent on order id"]
|
|
99
|
+
)
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Failures
|
|
103
|
+
|
|
104
|
+
| Symptom | Cause | What to do |
|
|
105
|
+
| --- | --- | --- |
|
|
106
|
+
| Write rejected as read-only | The API key has read access only | Tell the user the key needs read + write access, created in project settings. Do not retry. |
|
|
107
|
+
| Write to `sources/` or `uploads/` fails | Those domains are read-only by design | Write to `documents/` instead |
|
|
108
|
+
| Write to a linked project's path fails | The link to that project does not grant write access | Ask the user to adjust the project link, or write into the current project |
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
|
|
4
|
+
import { mergeServer, readConfig, removeServer, writeConfig } from "../mcp-config.mjs";
|
|
5
|
+
|
|
6
|
+
export const SERVER_NAME = "jitera";
|
|
7
|
+
const CONFIG_KEY = "mcpServers";
|
|
8
|
+
const API_KEY_ENV = "JITERA_API_KEY";
|
|
9
|
+
|
|
10
|
+
export const cursor = {
|
|
11
|
+
id: "cursor",
|
|
12
|
+
label: "Cursor",
|
|
13
|
+
secretStrategy: "env",
|
|
14
|
+
|
|
15
|
+
detect({ home }) {
|
|
16
|
+
return existsSync(join(home, ".cursor"));
|
|
17
|
+
},
|
|
18
|
+
|
|
19
|
+
mcpConfigPath({ scope, home, cwd }) {
|
|
20
|
+
return scope === "user"
|
|
21
|
+
? join(home, ".cursor", "mcp.json")
|
|
22
|
+
: join(cwd, ".cursor", "mcp.json");
|
|
23
|
+
},
|
|
24
|
+
|
|
25
|
+
skillsDirs({ scope, home, cwd }) {
|
|
26
|
+
return scope === "user"
|
|
27
|
+
? [join(home, ".agents", "skills"), join(home, ".claude", "skills")]
|
|
28
|
+
: [join(cwd, ".agents", "skills"), join(cwd, ".claude", "skills")];
|
|
29
|
+
},
|
|
30
|
+
|
|
31
|
+
serverEntry({ mcpUrl }) {
|
|
32
|
+
return {
|
|
33
|
+
type: "http",
|
|
34
|
+
url: mcpUrl,
|
|
35
|
+
headers: {
|
|
36
|
+
Authorization: `Bearer \${env:${API_KEY_ENV}}`,
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
install({ scope, home, cwd, mcpUrl, dryRun }) {
|
|
42
|
+
const path = this.mcpConfigPath({ scope, home, cwd });
|
|
43
|
+
const before = readConfig(path);
|
|
44
|
+
const after = mergeServer(before, CONFIG_KEY, SERVER_NAME, this.serverEntry({ mcpUrl }));
|
|
45
|
+
if (!dryRun) writeConfig(path, after);
|
|
46
|
+
return { path, config: after, changed: JSON.stringify(before) !== JSON.stringify(after) };
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
uninstall({ scope, home, cwd, dryRun }) {
|
|
50
|
+
const path = this.mcpConfigPath({ scope, home, cwd });
|
|
51
|
+
const before = readConfig(path);
|
|
52
|
+
const after = removeServer(before, CONFIG_KEY, SERVER_NAME);
|
|
53
|
+
if (!dryRun) writeConfig(path, after);
|
|
54
|
+
return { path, config: after, changed: JSON.stringify(before) !== JSON.stringify(after) };
|
|
55
|
+
},
|
|
56
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
const PRODUCTION_GATEWAY = "https://gateway-proxy.jitera.app";
|
|
2
|
+
const STAGE_GATEWAY = "https://jitera-stage-pilot.jitera.app";
|
|
3
|
+
const PILOT_GATEWAY = "https://kong-proxy-pilot.jitera.app";
|
|
4
|
+
|
|
5
|
+
const PILOT_PATTERN = /^studio-(\d{1,2})$/;
|
|
6
|
+
|
|
7
|
+
export const SUPPORTED_ENVIRONMENTS = ["studio", "studio-stage", "studio-01", "studio-06"];
|
|
8
|
+
|
|
9
|
+
export class UnknownEnvironmentError extends Error {
|
|
10
|
+
constructor(value) {
|
|
11
|
+
super(
|
|
12
|
+
`unknown environment "${value}". Supported: omit the flag for production, ` +
|
|
13
|
+
`--env=studio-stage for staging, or --env=studio-NN for a numbered pilot ` +
|
|
14
|
+
`such as --env=studio-06.`
|
|
15
|
+
);
|
|
16
|
+
this.name = "UnknownEnvironmentError";
|
|
17
|
+
this.value = value;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function resolveBase(environment) {
|
|
22
|
+
const name = String(environment ?? "").trim().toLowerCase();
|
|
23
|
+
|
|
24
|
+
if (name === "" || name === "studio" || name === "production") {
|
|
25
|
+
return `${PRODUCTION_GATEWAY}/gateway/boost`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (name === "studio-stage") {
|
|
29
|
+
return `${STAGE_GATEWAY}/gateway/boost`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const pilot = PILOT_PATTERN.exec(name);
|
|
33
|
+
if (pilot) {
|
|
34
|
+
return `${PILOT_GATEWAY}/gateway/boost-${pilot[1].padStart(2, "0")}`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
throw new UnknownEnvironmentError(environment);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function resolveMcpUrl(environment) {
|
|
41
|
+
return `${resolveBase(environment)}/mcp`;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function resolveApiBaseUrl(environment) {
|
|
45
|
+
return `${resolveBase(environment)}/v1`;
|
|
46
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname } from "node:path";
|
|
3
|
+
|
|
4
|
+
export class MalformedConfigError extends Error {
|
|
5
|
+
constructor(path, cause) {
|
|
6
|
+
super(
|
|
7
|
+
`${path} exists but is not valid JSON, so it cannot be merged safely. ` +
|
|
8
|
+
`Fix or move the file, then run again. Parser said: ${cause.message}`
|
|
9
|
+
);
|
|
10
|
+
this.name = "MalformedConfigError";
|
|
11
|
+
this.path = path;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function readConfig(path) {
|
|
16
|
+
if (!existsSync(path)) return {};
|
|
17
|
+
const raw = readFileSync(path, "utf8");
|
|
18
|
+
if (raw.trim() === "") return {};
|
|
19
|
+
try {
|
|
20
|
+
return JSON.parse(raw);
|
|
21
|
+
} catch (cause) {
|
|
22
|
+
throw new MalformedConfigError(path, cause);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function mergeServer(config, key, name, server) {
|
|
27
|
+
const next = { ...config };
|
|
28
|
+
next[key] = { ...(next[key] ?? {}) };
|
|
29
|
+
next[key][name] = server;
|
|
30
|
+
return next;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function removeServer(config, key, name) {
|
|
34
|
+
const next = { ...config };
|
|
35
|
+
if (!next[key]) return next;
|
|
36
|
+
next[key] = { ...next[key] };
|
|
37
|
+
delete next[key][name];
|
|
38
|
+
if (Object.keys(next[key]).length === 0) delete next[key];
|
|
39
|
+
return next;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function writeConfig(path, config) {
|
|
43
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
44
|
+
writeFileSync(path, `${JSON.stringify(config, undefined, 2)}\n`, "utf8");
|
|
45
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
<!-- BEGIN JITERA CONNECT -->
|
|
2
|
+
## {{BRAND}} project context
|
|
3
|
+
|
|
4
|
+
This repository is connected to a {{BRAND}} project. Documentation, source, and a
|
|
5
|
+
shared project memory are available through the `jitera` MCP server.
|
|
6
|
+
|
|
7
|
+
Before planning or implementing anything, call `recall_jitera_memory`. An empty
|
|
8
|
+
result is normal and is not an error.
|
|
9
|
+
|
|
10
|
+
Before writing a spec or design, search `documents/` with `resource_search` and
|
|
11
|
+
read any match with `resource_read`. Update the existing document rather than
|
|
12
|
+
creating a second one.
|
|
13
|
+
|
|
14
|
+
When a decision is made, a constraint is discovered, or you are corrected,
|
|
15
|
+
persist it with `remember_jitera_memory`. Recall the entity first and re-send its
|
|
16
|
+
full attribute list, because entities are keyed on name plus type.
|
|
17
|
+
|
|
18
|
+
`documents/` is readable and writable. `sources/` and `uploads/` are read-only.
|
|
19
|
+
|
|
20
|
+
Claude Code does not read this file. Add `@AGENTS.md` to `CLAUDE.md` so both read
|
|
21
|
+
the same instructions.
|
|
22
|
+
<!-- END JITERA CONNECT -->
|