tlc-claude-code 2.0.1 → 2.1.0
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/commands/tlc/deploy.md +194 -2
- package/.claude/commands/tlc/e2e-verify.md +214 -0
- package/.claude/commands/tlc/guard.md +191 -0
- package/.claude/commands/tlc/help.md +32 -0
- package/.claude/commands/tlc/init.md +73 -37
- package/.claude/commands/tlc/llm.md +19 -4
- package/.claude/commands/tlc/preflight.md +134 -0
- package/.claude/commands/tlc/review.md +17 -4
- package/.claude/commands/tlc/watchci.md +159 -0
- package/.claude/hooks/tlc-block-tools.sh +41 -0
- package/.claude/hooks/tlc-capture-exchange.sh +50 -0
- package/.claude/hooks/tlc-post-build.sh +38 -0
- package/.claude/hooks/tlc-post-push.sh +22 -0
- package/.claude/hooks/tlc-prompt-guard.sh +69 -0
- package/.claude/hooks/tlc-session-init.sh +123 -0
- package/CLAUDE.md +12 -0
- package/bin/install.js +171 -2
- package/bin/postinstall.js +45 -26
- package/dashboard-web/dist/assets/index-CdS5CHqu.css +1 -0
- package/dashboard-web/dist/assets/index-CwNPPVpg.js +483 -0
- package/dashboard-web/dist/assets/index-CwNPPVpg.js.map +1 -0
- package/dashboard-web/dist/index.html +2 -2
- package/docker-compose.dev.yml +18 -12
- package/package.json +3 -1
- package/server/index.js +228 -2
- package/server/lib/capture-bridge.js +242 -0
- package/server/lib/capture-bridge.test.js +363 -0
- package/server/lib/capture-guard.js +140 -0
- package/server/lib/capture-guard.test.js +182 -0
- package/server/lib/command-runner.js +159 -0
- package/server/lib/command-runner.test.js +92 -0
- package/server/lib/deploy/runners/dependency-runner.js +106 -0
- package/server/lib/deploy/runners/dependency-runner.test.js +148 -0
- package/server/lib/deploy/runners/secrets-runner.js +174 -0
- package/server/lib/deploy/runners/secrets-runner.test.js +127 -0
- package/server/lib/deploy/security-gates.js +11 -24
- package/server/lib/deploy/security-gates.test.js +9 -2
- package/server/lib/deploy-engine.js +182 -0
- package/server/lib/deploy-engine.test.js +147 -0
- package/server/lib/docker-api.js +137 -0
- package/server/lib/docker-api.test.js +202 -0
- package/server/lib/docker-client.js +297 -0
- package/server/lib/docker-client.test.js +308 -0
- package/server/lib/input-sanitizer.js +86 -0
- package/server/lib/input-sanitizer.test.js +117 -0
- package/server/lib/launchd-agent.js +225 -0
- package/server/lib/launchd-agent.test.js +185 -0
- package/server/lib/memory-api.js +3 -1
- package/server/lib/memory-api.test.js +3 -5
- package/server/lib/memory-bridge-e2e.test.js +160 -0
- package/server/lib/memory-committer.js +18 -4
- package/server/lib/memory-committer.test.js +21 -0
- package/server/lib/memory-hooks-capture.test.js +69 -4
- package/server/lib/memory-hooks-integration.test.js +98 -0
- package/server/lib/memory-hooks.js +42 -4
- package/server/lib/memory-store-adapter.js +105 -0
- package/server/lib/memory-store-adapter.test.js +141 -0
- package/server/lib/memory-wiring-e2e.test.js +93 -0
- package/server/lib/nginx-config.js +114 -0
- package/server/lib/nginx-config.test.js +82 -0
- package/server/lib/ollama-health.js +91 -0
- package/server/lib/ollama-health.test.js +74 -0
- package/server/lib/port-guard.js +44 -0
- package/server/lib/port-guard.test.js +65 -0
- package/server/lib/project-scanner.js +37 -2
- package/server/lib/project-scanner.test.js +152 -0
- package/server/lib/remember-command.js +2 -0
- package/server/lib/remember-command.test.js +23 -0
- package/server/lib/security/crypto-utils.test.js +2 -2
- package/server/lib/semantic-recall.js +1 -1
- package/server/lib/semantic-recall.test.js +17 -0
- package/server/lib/ssh-client.js +184 -0
- package/server/lib/ssh-client.test.js +127 -0
- package/server/lib/vps-api.js +184 -0
- package/server/lib/vps-api.test.js +208 -0
- package/server/lib/vps-bootstrap.js +124 -0
- package/server/lib/vps-bootstrap.test.js +79 -0
- package/server/lib/vps-monitor.js +126 -0
- package/server/lib/vps-monitor.test.js +98 -0
- package/server/lib/workspace-api.js +182 -1
- package/server/lib/workspace-api.test.js +474 -0
- package/server/package-lock.json +737 -0
- package/server/package.json +3 -0
- package/dashboard-web/dist/assets/index-Uhc49PE-.css +0 -1
- package/dashboard-web/dist/assets/index-W36XHPC5.js +0 -431
- package/dashboard-web/dist/assets/index-W36XHPC5.js.map +0 -1
|
@@ -16,6 +16,8 @@ const { createTestInventory } = require('./test-inventory');
|
|
|
16
16
|
const { createRoadmapApi } = require('./roadmap-api');
|
|
17
17
|
const { createPlanWriter } = require('./plan-writer');
|
|
18
18
|
const { createBugWriter } = require('./bug-writer');
|
|
19
|
+
const { createMemoryStoreAdapter } = require('./memory-store-adapter');
|
|
20
|
+
const { createCaptureGuard } = require('./capture-guard');
|
|
19
21
|
|
|
20
22
|
/**
|
|
21
23
|
* Encode a project path to a URL-safe project ID
|
|
@@ -62,6 +64,7 @@ function readProjectStatus(projectPath) {
|
|
|
62
64
|
phaseName: null,
|
|
63
65
|
totalPhases: 0,
|
|
64
66
|
completedPhases: 0,
|
|
67
|
+
coverage: null,
|
|
65
68
|
};
|
|
66
69
|
|
|
67
70
|
if (!status.exists) {
|
|
@@ -128,6 +131,19 @@ function readProjectStatus(projectPath) {
|
|
|
128
131
|
}
|
|
129
132
|
}
|
|
130
133
|
|
|
134
|
+
// Read coverage from Istanbul coverage-summary.json
|
|
135
|
+
const coveragePath = path.join(projectPath, 'coverage', 'coverage-summary.json');
|
|
136
|
+
if (fs.existsSync(coveragePath)) {
|
|
137
|
+
try {
|
|
138
|
+
const covData = JSON.parse(fs.readFileSync(coveragePath, 'utf-8'));
|
|
139
|
+
if (covData.total && covData.total.lines && typeof covData.total.lines.pct === 'number') {
|
|
140
|
+
status.coverage = covData.total.lines.pct;
|
|
141
|
+
}
|
|
142
|
+
} catch {
|
|
143
|
+
// Malformed coverage file — leave as null
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
131
147
|
return status;
|
|
132
148
|
}
|
|
133
149
|
|
|
@@ -292,7 +308,7 @@ function readProjectBugs(projectPath) {
|
|
|
292
308
|
* @returns {express.Router} Express router with workspace endpoints
|
|
293
309
|
*/
|
|
294
310
|
function createWorkspaceRouter(options = {}) {
|
|
295
|
-
const { globalConfig, projectScanner } = options;
|
|
311
|
+
const { globalConfig, projectScanner, memoryApi, memoryDeps = {} } = options;
|
|
296
312
|
|
|
297
313
|
if (!globalConfig) {
|
|
298
314
|
throw new Error('globalConfig is required');
|
|
@@ -302,6 +318,7 @@ function createWorkspaceRouter(options = {}) {
|
|
|
302
318
|
}
|
|
303
319
|
|
|
304
320
|
const router = express.Router();
|
|
321
|
+
const captureGuard = createCaptureGuard();
|
|
305
322
|
|
|
306
323
|
// =========================================================================
|
|
307
324
|
// GET /config - Returns workspace configuration
|
|
@@ -805,6 +822,170 @@ function createWorkspaceRouter(options = {}) {
|
|
|
805
822
|
}
|
|
806
823
|
});
|
|
807
824
|
|
|
825
|
+
// =========================================================================
|
|
826
|
+
// Memory API routes (Phase 77, fixed per-project in Phase 78)
|
|
827
|
+
// =========================================================================
|
|
828
|
+
if (memoryApi) {
|
|
829
|
+
router.get('/projects/:projectId/memory/decisions', async (req, res) => {
|
|
830
|
+
try {
|
|
831
|
+
const roots = globalConfig.getRoots();
|
|
832
|
+
const project = findProjectById(projectScanner, roots, req.params.projectId);
|
|
833
|
+
if (!project) return res.status(404).json({ error: 'Project not found' });
|
|
834
|
+
const adapter = createMemoryStoreAdapter(project.path);
|
|
835
|
+
const decisions = await adapter.listDecisions();
|
|
836
|
+
res.json({ decisions });
|
|
837
|
+
} catch (err) {
|
|
838
|
+
res.status(500).json({ error: err.message });
|
|
839
|
+
}
|
|
840
|
+
});
|
|
841
|
+
|
|
842
|
+
router.get('/projects/:projectId/memory/gotchas', async (req, res) => {
|
|
843
|
+
try {
|
|
844
|
+
const roots = globalConfig.getRoots();
|
|
845
|
+
const project = findProjectById(projectScanner, roots, req.params.projectId);
|
|
846
|
+
if (!project) return res.status(404).json({ error: 'Project not found' });
|
|
847
|
+
const adapter = createMemoryStoreAdapter(project.path);
|
|
848
|
+
const gotchas = await adapter.listGotchas();
|
|
849
|
+
res.json({ gotchas });
|
|
850
|
+
} catch (err) {
|
|
851
|
+
res.status(500).json({ error: err.message });
|
|
852
|
+
}
|
|
853
|
+
});
|
|
854
|
+
|
|
855
|
+
router.get('/projects/:projectId/memory/stats', async (req, res) => {
|
|
856
|
+
try {
|
|
857
|
+
const roots = globalConfig.getRoots();
|
|
858
|
+
const project = findProjectById(projectScanner, roots, req.params.projectId);
|
|
859
|
+
if (!project) return res.status(404).json({ error: 'Project not found' });
|
|
860
|
+
const adapter = createMemoryStoreAdapter(project.path);
|
|
861
|
+
const stats = await adapter.getStats();
|
|
862
|
+
res.json(stats);
|
|
863
|
+
} catch (err) {
|
|
864
|
+
res.status(500).json({ error: err.message });
|
|
865
|
+
}
|
|
866
|
+
});
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
// =========================================================================
|
|
870
|
+
// Memory capture endpoint (Phase 79 Task 5)
|
|
871
|
+
// =========================================================================
|
|
872
|
+
router.post('/projects/:projectId/memory/capture', async (req, res) => {
|
|
873
|
+
try {
|
|
874
|
+
const projectId = req.params.projectId;
|
|
875
|
+
|
|
876
|
+
// Rate limit check
|
|
877
|
+
const rateCheck = captureGuard.checkRateLimit(projectId);
|
|
878
|
+
if (!rateCheck.ok) {
|
|
879
|
+
return res.status(rateCheck.status).json({ error: rateCheck.error });
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
// Payload validation (size + structure)
|
|
883
|
+
const validation = captureGuard.validate(req.body, projectId);
|
|
884
|
+
if (!validation.ok) {
|
|
885
|
+
return res.status(validation.status).json({ error: validation.error });
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
const roots = globalConfig.getRoots();
|
|
889
|
+
const project = findProjectById(projectScanner, roots, projectId);
|
|
890
|
+
if (!project) return res.status(404).json({ error: 'Project not found' });
|
|
891
|
+
|
|
892
|
+
// Deduplicate exchanges
|
|
893
|
+
const exchanges = captureGuard.deduplicate(req.body.exchanges, projectId);
|
|
894
|
+
|
|
895
|
+
if (exchanges.length === 0) {
|
|
896
|
+
return res.json({ captured: 0, deduplicated: true });
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
// Process in background — respond immediately
|
|
900
|
+
let captured = 0;
|
|
901
|
+
const { observeAndRemember, vectorIndexer } = memoryDeps;
|
|
902
|
+
|
|
903
|
+
for (const exchange of exchanges) {
|
|
904
|
+
try {
|
|
905
|
+
if (typeof observeAndRemember === 'function') {
|
|
906
|
+
await observeAndRemember(project.path, exchange);
|
|
907
|
+
}
|
|
908
|
+
if (vectorIndexer && typeof vectorIndexer.indexChunk === 'function') {
|
|
909
|
+
await vectorIndexer.indexChunk(exchange);
|
|
910
|
+
}
|
|
911
|
+
captured++;
|
|
912
|
+
} catch {
|
|
913
|
+
// Individual exchange failures don't stop the batch
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
res.json({ captured });
|
|
918
|
+
} catch (err) {
|
|
919
|
+
res.status(500).json({ error: err.message });
|
|
920
|
+
}
|
|
921
|
+
});
|
|
922
|
+
|
|
923
|
+
// =========================================================================
|
|
924
|
+
// Memory search endpoint (Phase 79 Task 6)
|
|
925
|
+
// =========================================================================
|
|
926
|
+
router.get('/projects/:projectId/memory/search', async (req, res) => {
|
|
927
|
+
try {
|
|
928
|
+
const roots = globalConfig.getRoots();
|
|
929
|
+
const project = findProjectById(projectScanner, roots, req.params.projectId);
|
|
930
|
+
if (!project) return res.status(404).json({ error: 'Project not found' });
|
|
931
|
+
|
|
932
|
+
const query = req.query.q;
|
|
933
|
+
if (!query) {
|
|
934
|
+
return res.status(400).json({ error: 'Query parameter q is required' });
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
const { semanticRecall } = memoryDeps;
|
|
938
|
+
|
|
939
|
+
// Try vector-based semantic recall first
|
|
940
|
+
if (semanticRecall && typeof semanticRecall.recall === 'function') {
|
|
941
|
+
try {
|
|
942
|
+
const results = await semanticRecall.recall(query, { projectRoot: project.path });
|
|
943
|
+
return res.json({ results: results || [], source: 'vector' });
|
|
944
|
+
} catch {
|
|
945
|
+
// Fall through to file-based search
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
// Fallback: file-based text search
|
|
950
|
+
try {
|
|
951
|
+
const { searchMemory } = require('./memory-reader');
|
|
952
|
+
const results = await searchMemory(project.path, query);
|
|
953
|
+
return res.json({ results: results || [], source: 'file' });
|
|
954
|
+
} catch {
|
|
955
|
+
return res.json({ results: [], source: 'file' });
|
|
956
|
+
}
|
|
957
|
+
} catch (err) {
|
|
958
|
+
res.status(500).json({ error: err.message });
|
|
959
|
+
}
|
|
960
|
+
});
|
|
961
|
+
|
|
962
|
+
// =========================================================================
|
|
963
|
+
// Project file endpoint (Phase 77)
|
|
964
|
+
// =========================================================================
|
|
965
|
+
router.get('/projects/:projectId/files/:filename', (req, res) => {
|
|
966
|
+
try {
|
|
967
|
+
const roots = globalConfig.getRoots();
|
|
968
|
+
const project = findProjectById(projectScanner, roots, req.params.projectId);
|
|
969
|
+
if (!project) return res.status(404).json({ error: 'Project not found' });
|
|
970
|
+
|
|
971
|
+
const filename = req.params.filename;
|
|
972
|
+
// Reject path traversal
|
|
973
|
+
if (filename.includes('..') || filename.includes('/') || filename.includes('\\')) {
|
|
974
|
+
return res.status(400).json({ error: 'Invalid filename' });
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
const filePath = path.join(project.path, '.planning', filename);
|
|
978
|
+
if (!fs.existsSync(filePath)) {
|
|
979
|
+
return res.status(404).json({ error: 'File not found' });
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
983
|
+
res.json({ filename, content });
|
|
984
|
+
} catch (err) {
|
|
985
|
+
res.status(500).json({ error: err.message });
|
|
986
|
+
}
|
|
987
|
+
});
|
|
988
|
+
|
|
808
989
|
return router;
|
|
809
990
|
}
|
|
810
991
|
|