@gitgov/core 1.0.2 → 1.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/dist/src/index.d.ts +20 -2
- package/dist/src/index.js +227 -10
- package/dist/src/index.js.map +1 -1
- package/package.json +1 -1
package/dist/src/index.d.ts
CHANGED
|
@@ -1763,6 +1763,7 @@ interface IBacklogAdapter {
|
|
|
1763
1763
|
updateTask(taskId: string, payload: Partial<TaskRecord>): Promise<TaskRecord>;
|
|
1764
1764
|
activateTask(taskId: string, actorId: string): Promise<TaskRecord>;
|
|
1765
1765
|
completeTask(taskId: string, actorId: string): Promise<TaskRecord>;
|
|
1766
|
+
pauseTask(taskId: string, actorId: string, reason?: string): Promise<TaskRecord>;
|
|
1766
1767
|
resumeTask(taskId: string, actorId: string, force?: boolean): Promise<TaskRecord>;
|
|
1767
1768
|
discardTask(taskId: string, actorId: string, reason?: string): Promise<TaskRecord>;
|
|
1768
1769
|
createCycle(payload: Partial<CycleRecord>, actorId: string): Promise<CycleRecord>;
|
|
@@ -1770,6 +1771,8 @@ interface IBacklogAdapter {
|
|
|
1770
1771
|
getAllCycles(): Promise<CycleRecord[]>;
|
|
1771
1772
|
updateCycle(cycleId: string, payload: Partial<CycleRecord>): Promise<CycleRecord>;
|
|
1772
1773
|
addTaskToCycle(cycleId: string, taskId: string): Promise<void>;
|
|
1774
|
+
removeTasksFromCycle(cycleId: string, taskIds: string[]): Promise<void>;
|
|
1775
|
+
moveTasksBetweenCycles(targetCycleId: string, taskIds: string[], sourceCycleId: string): Promise<void>;
|
|
1773
1776
|
getTasksAssignedToActor(actorId: string): Promise<TaskRecord[]>;
|
|
1774
1777
|
handleFeedbackCreated(event: FeedbackCreatedEvent): Promise<void>;
|
|
1775
1778
|
handleFeedbackResolved(event: FeedbackStatusChangedEvent): Promise<void>;
|
|
@@ -1845,6 +1848,10 @@ declare class BacklogAdapter implements IBacklogAdapter {
|
|
|
1845
1848
|
* Activates a task transitioning from ready to active with permission validation
|
|
1846
1849
|
*/
|
|
1847
1850
|
activateTask(taskId: string, actorId: string): Promise<TaskRecord>;
|
|
1851
|
+
/**
|
|
1852
|
+
* Pauses a task manually transitioning from active to paused with optional reason
|
|
1853
|
+
*/
|
|
1854
|
+
pauseTask(taskId: string, actorId: string, reason?: string): Promise<TaskRecord>;
|
|
1848
1855
|
/**
|
|
1849
1856
|
* Resumes a paused task transitioning back to active with optional force override
|
|
1850
1857
|
*/
|
|
@@ -1926,6 +1933,17 @@ declare class BacklogAdapter implements IBacklogAdapter {
|
|
|
1926
1933
|
* Creates bidirectional link between task and cycle
|
|
1927
1934
|
*/
|
|
1928
1935
|
addTaskToCycle(cycleId: string, taskId: string): Promise<void>;
|
|
1936
|
+
/**
|
|
1937
|
+
* Removes multiple tasks from a cycle with bidirectional unlinking
|
|
1938
|
+
* All business logic and validation happens here in the adapter
|
|
1939
|
+
*/
|
|
1940
|
+
removeTasksFromCycle(cycleId: string, taskIds: string[]): Promise<void>;
|
|
1941
|
+
/**
|
|
1942
|
+
* Moves multiple tasks from one cycle to another atomically
|
|
1943
|
+
* Provides transactional semantics - all tasks move or none do
|
|
1944
|
+
* All business logic and validation happens here in the adapter
|
|
1945
|
+
*/
|
|
1946
|
+
moveTasksBetweenCycles(targetCycleId: string, taskIds: string[], sourceCycleId: string): Promise<void>;
|
|
1929
1947
|
lint(): Promise<LintReport>;
|
|
1930
1948
|
audit(): Promise<AuditReport>;
|
|
1931
1949
|
processChanges(_changes: unknown[]): Promise<ExecutionRecord[]>;
|
|
@@ -5405,7 +5423,7 @@ declare class DiagramGenerator {
|
|
|
5405
5423
|
cycleId?: string;
|
|
5406
5424
|
taskId?: string;
|
|
5407
5425
|
packageName?: string;
|
|
5408
|
-
}): Promise<string>;
|
|
5426
|
+
}, showArchived?: boolean): Promise<string>;
|
|
5409
5427
|
/**
|
|
5410
5428
|
* Convenience method to generate from .gitgov/ directory
|
|
5411
5429
|
*/
|
|
@@ -5413,7 +5431,7 @@ declare class DiagramGenerator {
|
|
|
5413
5431
|
cycleId?: string;
|
|
5414
5432
|
taskId?: string;
|
|
5415
5433
|
packageName?: string;
|
|
5416
|
-
}): Promise<string>;
|
|
5434
|
+
}, showArchived?: boolean): Promise<string>;
|
|
5417
5435
|
/**
|
|
5418
5436
|
* Loads all cycle records from the filesystem
|
|
5419
5437
|
*/
|
package/dist/src/index.js
CHANGED
|
@@ -4236,6 +4236,55 @@ var BacklogAdapter = class {
|
|
|
4236
4236
|
});
|
|
4237
4237
|
return updatedPayload;
|
|
4238
4238
|
}
|
|
4239
|
+
/**
|
|
4240
|
+
* Pauses a task manually transitioning from active to paused with optional reason
|
|
4241
|
+
*/
|
|
4242
|
+
async pauseTask(taskId, actorId, reason) {
|
|
4243
|
+
const taskRecord = await this.taskStore.read(taskId);
|
|
4244
|
+
if (!taskRecord) {
|
|
4245
|
+
throw new Error(`RecordNotFoundError: Task not found: ${taskId}`);
|
|
4246
|
+
}
|
|
4247
|
+
const task = taskRecord.payload;
|
|
4248
|
+
if (task.status !== "active") {
|
|
4249
|
+
throw new Error(`ProtocolViolationError: Task is in '${task.status}' state. Cannot pause (requires active).`);
|
|
4250
|
+
}
|
|
4251
|
+
const actor = await this.getActor(actorId);
|
|
4252
|
+
const context = {
|
|
4253
|
+
task,
|
|
4254
|
+
actor,
|
|
4255
|
+
signatures: taskRecord.header.signatures,
|
|
4256
|
+
transitionTo: "paused"
|
|
4257
|
+
};
|
|
4258
|
+
const transitionRule = await this.workflowMethodologyAdapter.getTransitionRule("active", "paused", context);
|
|
4259
|
+
if (!transitionRule) {
|
|
4260
|
+
throw new Error("ProtocolViolationError: Workflow methodology rejected active\u2192paused transition");
|
|
4261
|
+
}
|
|
4262
|
+
const updatedPayload = {
|
|
4263
|
+
...task,
|
|
4264
|
+
status: "paused",
|
|
4265
|
+
// Add reason to notes with [PAUSED] prefix if provided
|
|
4266
|
+
...reason && {
|
|
4267
|
+
notes: `${task.notes || ""}
|
|
4268
|
+
[PAUSED] ${reason} (${(/* @__PURE__ */ new Date()).toISOString()})`.trim()
|
|
4269
|
+
}
|
|
4270
|
+
};
|
|
4271
|
+
const updatedRecord = { ...taskRecord, payload: updatedPayload };
|
|
4272
|
+
const signedRecord = await this.identity.signRecord(updatedRecord, actorId, "pauser");
|
|
4273
|
+
await this.taskStore.write(signedRecord);
|
|
4274
|
+
this.eventBus.publish({
|
|
4275
|
+
type: "task.status.changed",
|
|
4276
|
+
timestamp: Date.now(),
|
|
4277
|
+
source: "backlog_adapter",
|
|
4278
|
+
payload: {
|
|
4279
|
+
taskId,
|
|
4280
|
+
oldStatus: "active",
|
|
4281
|
+
newStatus: "paused",
|
|
4282
|
+
actorId,
|
|
4283
|
+
reason: reason || "Task manually paused"
|
|
4284
|
+
}
|
|
4285
|
+
});
|
|
4286
|
+
return updatedPayload;
|
|
4287
|
+
}
|
|
4239
4288
|
/**
|
|
4240
4289
|
* Resumes a paused task transitioning back to active with optional force override
|
|
4241
4290
|
*/
|
|
@@ -4801,6 +4850,156 @@ ${task.status === "review" ? "[REJECTED]" : "[CANCELLED]"} ${reason} (${(/* @__P
|
|
|
4801
4850
|
this.taskStore.write(signedTaskRecord)
|
|
4802
4851
|
]);
|
|
4803
4852
|
}
|
|
4853
|
+
/**
|
|
4854
|
+
* Removes multiple tasks from a cycle with bidirectional unlinking
|
|
4855
|
+
* All business logic and validation happens here in the adapter
|
|
4856
|
+
*/
|
|
4857
|
+
async removeTasksFromCycle(cycleId, taskIds) {
|
|
4858
|
+
if (!cycleId || typeof cycleId !== "string") {
|
|
4859
|
+
throw new Error("ValidationError: cycleId must be a non-empty string");
|
|
4860
|
+
}
|
|
4861
|
+
if (!Array.isArray(taskIds) || taskIds.length === 0) {
|
|
4862
|
+
throw new Error("ValidationError: taskIds must be a non-empty array");
|
|
4863
|
+
}
|
|
4864
|
+
const cycleRecord = await this.cycleStore.read(cycleId);
|
|
4865
|
+
if (!cycleRecord) {
|
|
4866
|
+
throw new Error(`RecordNotFoundError: Cycle not found: ${cycleId}`);
|
|
4867
|
+
}
|
|
4868
|
+
const taskRecords = await Promise.all(
|
|
4869
|
+
taskIds.map(async (taskId) => {
|
|
4870
|
+
const taskRecord = await this.taskStore.read(taskId);
|
|
4871
|
+
if (!taskRecord) {
|
|
4872
|
+
throw new Error(`RecordNotFoundError: Task not found: ${taskId}`);
|
|
4873
|
+
}
|
|
4874
|
+
return { taskId, record: taskRecord };
|
|
4875
|
+
})
|
|
4876
|
+
);
|
|
4877
|
+
const cycleTaskIds = cycleRecord.payload.taskIds || [];
|
|
4878
|
+
const notLinkedTasks = taskIds.filter((taskId) => !cycleTaskIds.includes(taskId));
|
|
4879
|
+
if (notLinkedTasks.length > 0) {
|
|
4880
|
+
throw new Error(`ValidationError: Tasks not linked to cycle ${cycleId}: ${notLinkedTasks.join(", ")}`);
|
|
4881
|
+
}
|
|
4882
|
+
const updatedCycle = {
|
|
4883
|
+
...cycleRecord.payload,
|
|
4884
|
+
taskIds: cycleTaskIds.filter((id) => !taskIds.includes(id))
|
|
4885
|
+
};
|
|
4886
|
+
const currentActor = await this.identity.getCurrentActor();
|
|
4887
|
+
const signedCycleRecord = await this.identity.signRecord(
|
|
4888
|
+
{ ...cycleRecord, payload: updatedCycle },
|
|
4889
|
+
currentActor.id,
|
|
4890
|
+
"author"
|
|
4891
|
+
);
|
|
4892
|
+
const signedTaskRecords = await Promise.all(
|
|
4893
|
+
taskRecords.map(async ({ record }) => {
|
|
4894
|
+
const taskCycleIds = record.payload.cycleIds || [];
|
|
4895
|
+
const updatedTask = {
|
|
4896
|
+
...record.payload,
|
|
4897
|
+
cycleIds: taskCycleIds.filter((id) => id !== cycleId)
|
|
4898
|
+
};
|
|
4899
|
+
return await this.identity.signRecord(
|
|
4900
|
+
{ ...record, payload: updatedTask },
|
|
4901
|
+
currentActor.id,
|
|
4902
|
+
"author"
|
|
4903
|
+
);
|
|
4904
|
+
})
|
|
4905
|
+
);
|
|
4906
|
+
await Promise.all([
|
|
4907
|
+
this.cycleStore.write(signedCycleRecord),
|
|
4908
|
+
...signedTaskRecords.map(
|
|
4909
|
+
(signedTask) => this.taskStore.write(signedTask)
|
|
4910
|
+
)
|
|
4911
|
+
]);
|
|
4912
|
+
}
|
|
4913
|
+
/**
|
|
4914
|
+
* Moves multiple tasks from one cycle to another atomically
|
|
4915
|
+
* Provides transactional semantics - all tasks move or none do
|
|
4916
|
+
* All business logic and validation happens here in the adapter
|
|
4917
|
+
*/
|
|
4918
|
+
async moveTasksBetweenCycles(targetCycleId, taskIds, sourceCycleId) {
|
|
4919
|
+
if (!sourceCycleId || typeof sourceCycleId !== "string") {
|
|
4920
|
+
throw new Error("ValidationError: sourceCycleId must be a non-empty string");
|
|
4921
|
+
}
|
|
4922
|
+
if (!targetCycleId || typeof targetCycleId !== "string") {
|
|
4923
|
+
throw new Error("ValidationError: targetCycleId must be a non-empty string");
|
|
4924
|
+
}
|
|
4925
|
+
if (!Array.isArray(taskIds) || taskIds.length === 0) {
|
|
4926
|
+
throw new Error("ValidationError: taskIds must be a non-empty array");
|
|
4927
|
+
}
|
|
4928
|
+
if (sourceCycleId === targetCycleId) {
|
|
4929
|
+
throw new Error("ValidationError: Source and target cycles must be different");
|
|
4930
|
+
}
|
|
4931
|
+
const [sourceCycleRecord, targetCycleRecord] = await Promise.all([
|
|
4932
|
+
this.cycleStore.read(sourceCycleId),
|
|
4933
|
+
this.cycleStore.read(targetCycleId)
|
|
4934
|
+
]);
|
|
4935
|
+
if (!sourceCycleRecord) {
|
|
4936
|
+
throw new Error(`RecordNotFoundError: Source cycle not found: ${sourceCycleId}`);
|
|
4937
|
+
}
|
|
4938
|
+
if (!targetCycleRecord) {
|
|
4939
|
+
throw new Error(`RecordNotFoundError: Target cycle not found: ${targetCycleId}`);
|
|
4940
|
+
}
|
|
4941
|
+
const taskRecords = await Promise.all(
|
|
4942
|
+
taskIds.map(async (taskId) => {
|
|
4943
|
+
const taskRecord = await this.taskStore.read(taskId);
|
|
4944
|
+
if (!taskRecord) {
|
|
4945
|
+
throw new Error(`RecordNotFoundError: Task not found: ${taskId}`);
|
|
4946
|
+
}
|
|
4947
|
+
return { taskId, record: taskRecord };
|
|
4948
|
+
})
|
|
4949
|
+
);
|
|
4950
|
+
const sourceTaskIds = sourceCycleRecord.payload.taskIds || [];
|
|
4951
|
+
const notLinkedTasks = taskIds.filter((taskId) => !sourceTaskIds.includes(taskId));
|
|
4952
|
+
if (notLinkedTasks.length > 0) {
|
|
4953
|
+
throw new Error(`ValidationError: Tasks not linked to source cycle ${sourceCycleId}: ${notLinkedTasks.join(", ")}`);
|
|
4954
|
+
}
|
|
4955
|
+
const updatedSourceCycle = {
|
|
4956
|
+
...sourceCycleRecord.payload,
|
|
4957
|
+
taskIds: sourceTaskIds.filter((id) => !taskIds.includes(id))
|
|
4958
|
+
};
|
|
4959
|
+
const updatedTargetCycle = {
|
|
4960
|
+
...targetCycleRecord.payload,
|
|
4961
|
+
taskIds: [...targetCycleRecord.payload.taskIds || [], ...taskIds]
|
|
4962
|
+
};
|
|
4963
|
+
const currentActor = await this.identity.getCurrentActor();
|
|
4964
|
+
const [signedSourceCycle, signedTargetCycle] = await Promise.all([
|
|
4965
|
+
this.identity.signRecord(
|
|
4966
|
+
{ ...sourceCycleRecord, payload: updatedSourceCycle },
|
|
4967
|
+
currentActor.id,
|
|
4968
|
+
"author"
|
|
4969
|
+
),
|
|
4970
|
+
this.identity.signRecord(
|
|
4971
|
+
{ ...targetCycleRecord, payload: updatedTargetCycle },
|
|
4972
|
+
currentActor.id,
|
|
4973
|
+
"author"
|
|
4974
|
+
)
|
|
4975
|
+
]);
|
|
4976
|
+
const signedTaskRecords = await Promise.all(
|
|
4977
|
+
taskRecords.map(async ({ record }) => {
|
|
4978
|
+
const taskCycleIds = record.payload.cycleIds || [];
|
|
4979
|
+
const updatedTask = {
|
|
4980
|
+
...record.payload,
|
|
4981
|
+
cycleIds: taskCycleIds.filter((id) => id !== sourceCycleId).concat(targetCycleId)
|
|
4982
|
+
// Add target
|
|
4983
|
+
};
|
|
4984
|
+
return await this.identity.signRecord(
|
|
4985
|
+
{ ...record, payload: updatedTask },
|
|
4986
|
+
currentActor.id,
|
|
4987
|
+
"author"
|
|
4988
|
+
);
|
|
4989
|
+
})
|
|
4990
|
+
);
|
|
4991
|
+
try {
|
|
4992
|
+
await Promise.all([
|
|
4993
|
+
this.cycleStore.write(signedSourceCycle),
|
|
4994
|
+
this.cycleStore.write(signedTargetCycle),
|
|
4995
|
+
...signedTaskRecords.map(
|
|
4996
|
+
(signedTask) => this.taskStore.write(signedTask)
|
|
4997
|
+
)
|
|
4998
|
+
]);
|
|
4999
|
+
} catch (error) {
|
|
5000
|
+
throw new Error(`AtomicOperationError: Failed to move tasks between cycles: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
5001
|
+
}
|
|
5002
|
+
}
|
|
4804
5003
|
// TODO: Implement when lint_command.md is implemented
|
|
4805
5004
|
async lint() {
|
|
4806
5005
|
throw new Error("NotImplementedError: lint() will be implemented when lint_command.md is ready");
|
|
@@ -5442,7 +5641,18 @@ var ProjectAdapter = class {
|
|
|
5442
5641
|
{
|
|
5443
5642
|
type: "human",
|
|
5444
5643
|
displayName: options.actorName || "Project Owner",
|
|
5445
|
-
roles: [
|
|
5644
|
+
roles: [
|
|
5645
|
+
"admin",
|
|
5646
|
+
// Platform admin (future use)
|
|
5647
|
+
"author",
|
|
5648
|
+
// Create & submit tasks
|
|
5649
|
+
"approver:product",
|
|
5650
|
+
// Approve tasks (product decisions)
|
|
5651
|
+
"approver:quality",
|
|
5652
|
+
// Complete tasks (quality validation)
|
|
5653
|
+
"developer"
|
|
5654
|
+
// General development work
|
|
5655
|
+
]
|
|
5446
5656
|
},
|
|
5447
5657
|
"bootstrap"
|
|
5448
5658
|
);
|
|
@@ -5848,7 +6058,8 @@ var workflow_methodology_default_default = {
|
|
|
5848
6058
|
},
|
|
5849
6059
|
active: {
|
|
5850
6060
|
from: [
|
|
5851
|
-
"ready"
|
|
6061
|
+
"ready",
|
|
6062
|
+
"paused"
|
|
5852
6063
|
],
|
|
5853
6064
|
requires: {
|
|
5854
6065
|
event: "first_execution_record_created",
|
|
@@ -6018,7 +6229,8 @@ var workflow_methodology_scrum_default = {
|
|
|
6018
6229
|
},
|
|
6019
6230
|
active: {
|
|
6020
6231
|
from: [
|
|
6021
|
-
"ready"
|
|
6232
|
+
"ready",
|
|
6233
|
+
"paused"
|
|
6022
6234
|
],
|
|
6023
6235
|
requires: {
|
|
6024
6236
|
event: "sprint_started",
|
|
@@ -7714,8 +7926,8 @@ var DiagramGenerator = class {
|
|
|
7714
7926
|
/**
|
|
7715
7927
|
* Primary API - Performance optimized with caching
|
|
7716
7928
|
*/
|
|
7717
|
-
async generateFromRecords(cycles, tasks, filters) {
|
|
7718
|
-
const cacheKey = this.generateCacheKey(cycles, tasks);
|
|
7929
|
+
async generateFromRecords(cycles, tasks, filters, showArchived = false) {
|
|
7930
|
+
const cacheKey = this.generateCacheKey(cycles, tasks, showArchived);
|
|
7719
7931
|
if (this.cache.has(cacheKey)) {
|
|
7720
7932
|
this.metrics.incrementCacheHits();
|
|
7721
7933
|
return this.renderFromCache(cacheKey);
|
|
@@ -7724,8 +7936,12 @@ var DiagramGenerator = class {
|
|
|
7724
7936
|
try {
|
|
7725
7937
|
let finalCycles = cycles;
|
|
7726
7938
|
let finalTasks = tasks;
|
|
7939
|
+
if (!showArchived) {
|
|
7940
|
+
finalCycles = cycles.filter((cycle) => cycle.status !== "archived");
|
|
7941
|
+
finalTasks = tasks.filter((task) => task.status !== "archived");
|
|
7942
|
+
}
|
|
7727
7943
|
if (filters && (filters.cycleId || filters.taskId || filters.packageName)) {
|
|
7728
|
-
const filtered = this.analyzer.filterEntities(
|
|
7944
|
+
const filtered = this.analyzer.filterEntities(finalCycles, finalTasks, filters);
|
|
7729
7945
|
finalCycles = filtered.filteredCycles;
|
|
7730
7946
|
finalTasks = filtered.filteredTasks;
|
|
7731
7947
|
}
|
|
@@ -7743,10 +7959,10 @@ var DiagramGenerator = class {
|
|
|
7743
7959
|
/**
|
|
7744
7960
|
* Convenience method to generate from .gitgov/ directory
|
|
7745
7961
|
*/
|
|
7746
|
-
async generateFromFiles(gitgovPath = ".gitgov", filters) {
|
|
7962
|
+
async generateFromFiles(gitgovPath = ".gitgov", filters, showArchived = false) {
|
|
7747
7963
|
const cycles = await this.loadCycleRecords(gitgovPath);
|
|
7748
7964
|
const tasks = await this.loadTaskRecords(gitgovPath);
|
|
7749
|
-
return this.generateFromRecords(cycles, tasks, filters);
|
|
7965
|
+
return this.generateFromRecords(cycles, tasks, filters, showArchived);
|
|
7750
7966
|
}
|
|
7751
7967
|
/**
|
|
7752
7968
|
* Loads all cycle records from the filesystem
|
|
@@ -7830,13 +8046,14 @@ var DiagramGenerator = class {
|
|
|
7830
8046
|
/**
|
|
7831
8047
|
* Generates cache key for efficient lookups
|
|
7832
8048
|
*/
|
|
7833
|
-
generateCacheKey(cycles, tasks) {
|
|
8049
|
+
generateCacheKey(cycles, tasks, showArchived = false) {
|
|
7834
8050
|
const cycleIds = [...new Set(cycles.map((c) => c.id))].sort();
|
|
7835
8051
|
const taskIds = [...new Set(tasks.map((t) => t.id))].sort();
|
|
7836
8052
|
const cycleHash = this.hashArray(cycleIds);
|
|
7837
8053
|
const taskHash = this.hashArray(taskIds);
|
|
7838
8054
|
const optionsHash = this.hashString(JSON.stringify(this.options));
|
|
7839
|
-
|
|
8055
|
+
const archivedFlag = showArchived ? "with-archived" : "no-archived";
|
|
8056
|
+
return `diagram:${cycleHash}-${taskHash}-${optionsHash}-${archivedFlag}`;
|
|
7840
8057
|
}
|
|
7841
8058
|
/**
|
|
7842
8059
|
* Efficient hash function for arrays
|